diff --git a/demo/demo-etcd/README.md b/demo/demo-etcd/README.md new file mode 100644 index 0000000000..046bec6c96 --- /dev/null +++ b/demo/demo-etcd/README.md @@ -0,0 +1,5 @@ +# Notice + +This integration tests is designed for Etcd registry and configuration. And extra test cases include: + +* Test cases related to SpringMVC annotations that demo-springmvc can not cover. diff --git a/demo/demo-etcd/consumer/pom.xml b/demo/demo-etcd/consumer/pom.xml new file mode 100644 index 0000000000..cbccb498a4 --- /dev/null +++ b/demo/demo-etcd/consumer/pom.xml @@ -0,0 +1,88 @@ + + + + 4.0.0 + + + org.apache.servicecomb.demo + demo-etcd + 3.3.0-SNAPSHOT + + + etcd-consumer + Java Chassis::Demo::Etcd::CONSUMER + jar + + + + org.apache.servicecomb + java-chassis-spring-boot-starter-standalone + + + com.google.protobuf + protobuf-java + 3.25.3 + runtime + + + org.apache.servicecomb + registry-etcd + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + docker + + + + io.fabric8 + docker-maven-plugin + + + org.commonjava.maven.plugins + directory-maven-plugin + + + com.github.odavid.maven.plugins + mixin-maven-plugin + + + + org.apache.servicecomb.demo + docker-build-config + ${project.version} + + + + + + + + + diff --git a/demo/demo-etcd/consumer/src/main/java/org/apache/servicecomb/samples/ClientWebsocketController.java b/demo/demo-etcd/consumer/src/main/java/org/apache/servicecomb/samples/ClientWebsocketController.java new file mode 100644 index 0000000000..f71738a9ed --- /dev/null +++ b/demo/demo-etcd/consumer/src/main/java/org/apache/servicecomb/samples/ClientWebsocketController.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.apache.servicecomb.samples; + +import org.apache.servicecomb.core.CoreConst; +import org.apache.servicecomb.core.annotation.Transport; +import org.apache.servicecomb.provider.pojo.RpcReference; +import org.apache.servicecomb.provider.rest.common.RestSchema; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +import io.vertx.core.http.ServerWebSocket; +import io.vertx.core.http.WebSocket; + +@RestSchema(schemaId = "ClientWebsocketController") +@RequestMapping(path = "/ws") +public class ClientWebsocketController { + interface ProviderService { + WebSocket websocket(); + } + + @RpcReference(schemaId = "WebsocketController", microserviceName = "provider") + private ProviderService providerService; + + @PostMapping("/websocket") + @Transport(name = CoreConst.WEBSOCKET) + public void websocket(ServerWebSocket serverWebsocket) { + WebSocket providerWebSocket = providerService.websocket(); + providerWebSocket.closeHandler(v -> serverWebsocket.close()); + providerWebSocket.textMessageHandler(m -> { + serverWebsocket.writeTextMessage(m); + }); + serverWebsocket.textMessageHandler(m -> { + providerWebSocket.writeTextMessage(m); + }); + } +} diff --git a/demo/demo-etcd/consumer/src/main/java/org/apache/servicecomb/samples/ConsumerController.java b/demo/demo-etcd/consumer/src/main/java/org/apache/servicecomb/samples/ConsumerController.java new file mode 100644 index 0000000000..e5dd86d9d6 --- /dev/null +++ b/demo/demo-etcd/consumer/src/main/java/org/apache/servicecomb/samples/ConsumerController.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.apache.servicecomb.samples; + +import org.apache.servicecomb.provider.pojo.RpcReference; +import org.apache.servicecomb.provider.rest.common.RestSchema; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; + +@RestSchema(schemaId = "ConsumerController") +@RequestMapping(path = "/") +public class ConsumerController { + @RpcReference(schemaId = "ProviderController", microserviceName = "provider") + private ProviderService providerService; + + // consumer service which delegate the implementation to provider service. + @GetMapping("/sayHello") + public String sayHello(@RequestParam("name") String name) { + return providerService.sayHello(name); + } + + @GetMapping("/getConfig") + public String getConfig(@RequestParam("key") String key) { + return providerService.getConfig(key); + } +} diff --git a/demo/demo-etcd/consumer/src/main/java/org/apache/servicecomb/samples/ConsumerHeaderParamWithListSchema.java b/demo/demo-etcd/consumer/src/main/java/org/apache/servicecomb/samples/ConsumerHeaderParamWithListSchema.java new file mode 100644 index 0000000000..f0e894d674 --- /dev/null +++ b/demo/demo-etcd/consumer/src/main/java/org/apache/servicecomb/samples/ConsumerHeaderParamWithListSchema.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.apache.servicecomb.samples; + +import java.util.List; + +import org.apache.servicecomb.demo.api.IHeaderParamWithListSchemaSpringMvc; +import org.apache.servicecomb.provider.pojo.RpcReference; +import org.apache.servicecomb.provider.rest.common.RestSchema; + +@RestSchema(schemaId = "ConsumerHeaderParamWithListSchema", schemaInterface = IHeaderParamWithListSchemaSpringMvc.class) +public class ConsumerHeaderParamWithListSchema implements IHeaderParamWithListSchemaSpringMvc { + @RpcReference(microserviceName = "provider", schemaId = "HeaderParamWithListSchema") + private IHeaderParamWithListSchemaSpringMvc provider; + + @Override + public String headerListDefault(List headerList) { + return provider.headerListDefault(headerList); + } + + @Override + public String headerListCSV(List headerList) { + return provider.headerListCSV(headerList); + } + + @Override + public String headerListMULTI(List headerList) { + return provider.headerListMULTI(headerList); + } + + @Override + public String headerListSSV(List headerList) { + return provider.headerListSSV(headerList); + } + + @Override + public String headerListPIPES(List headerList) { + return provider.headerListPIPES(headerList); + } +} diff --git a/demo/demo-etcd/consumer/src/main/java/org/apache/servicecomb/samples/ConsumerReactiveStreamController.java b/demo/demo-etcd/consumer/src/main/java/org/apache/servicecomb/samples/ConsumerReactiveStreamController.java new file mode 100644 index 0000000000..aa169801cf --- /dev/null +++ b/demo/demo-etcd/consumer/src/main/java/org/apache/servicecomb/samples/ConsumerReactiveStreamController.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.apache.servicecomb.samples; + + +import org.apache.servicecomb.core.CoreConst; +import org.apache.servicecomb.core.annotation.Transport; +import org.apache.servicecomb.provider.pojo.RpcReference; +import org.apache.servicecomb.provider.rest.common.RestSchema; +import org.reactivestreams.Publisher; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +@RestSchema(schemaId = "ReactiveStreamController") +@RequestMapping(path = "/") +public class ConsumerReactiveStreamController { + interface ProviderReactiveStreamController { + Publisher sseString(); + + Publisher sseModel(); + } + + @RpcReference(microserviceName = "provider", schemaId = "ReactiveStreamController") + ProviderReactiveStreamController controller; + + public static class Model { + private String name; + + private int age; + + public Model() { + + } + + public Model(String name, int age) { + this.name = name; + this.age = age; + } + + public int getAge() { + return age; + } + + public Model setAge(int age) { + this.age = age; + return this; + } + + public String getName() { + return name; + } + + public Model setName(String name) { + this.name = name; + return this; + } + } + + @GetMapping("/sseString") + @Transport(name = CoreConst.RESTFUL) + public Publisher sseString() { + return controller.sseString(); + } + + @GetMapping("/sseModel") + @Transport(name = CoreConst.RESTFUL) + public Publisher sseModel() { + return controller.sseModel(); + } +} diff --git a/demo/demo-etcd/consumer/src/main/java/org/apache/servicecomb/samples/EtcdConsumerApplication.java b/demo/demo-etcd/consumer/src/main/java/org/apache/servicecomb/samples/EtcdConsumerApplication.java new file mode 100644 index 0000000000..2101e6512f --- /dev/null +++ b/demo/demo-etcd/consumer/src/main/java/org/apache/servicecomb/samples/EtcdConsumerApplication.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.apache.servicecomb.samples; + +import org.springframework.boot.WebApplicationType; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; + +@SpringBootApplication +public class EtcdConsumerApplication { + public static void main(String[] args) throws Exception { + try { + new SpringApplicationBuilder().web(WebApplicationType.NONE).sources(EtcdConsumerApplication.class).run(args); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/demo/demo-etcd/consumer/src/main/java/org/apache/servicecomb/samples/ProviderService.java b/demo/demo-etcd/consumer/src/main/java/org/apache/servicecomb/samples/ProviderService.java new file mode 100644 index 0000000000..fe71314c9f --- /dev/null +++ b/demo/demo-etcd/consumer/src/main/java/org/apache/servicecomb/samples/ProviderService.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.apache.servicecomb.samples; + +public interface ProviderService { + String sayHello(String name); + + String getConfig(String key); +} diff --git a/demo/demo-etcd/consumer/src/main/resources/application.yml b/demo/demo-etcd/consumer/src/main/resources/application.yml new file mode 100644 index 0000000000..a63a165189 --- /dev/null +++ b/demo/demo-etcd/consumer/src/main/resources/application.yml @@ -0,0 +1,32 @@ +# +## --------------------------------------------------------------------------- +## Licensed to the Apache Software Foundation (ASF) under one or more +## contributor license agreements. See the NOTICE file distributed with +## this work for additional information regarding copyright ownership. +## The ASF licenses this file to You under the Apache License, Version 2.0 +## (the "License"); you may not use this file except in compliance with +## the License. You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## 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. +## --------------------------------------------------------------------------- +servicecomb: + service: + application: demo-etcd + version: 0.0.1 + name: consumer + properties: + group: red + registry: + etcd: + connectString: http://127.0.0.1:2379 + + rest: + address: 0.0.0.0:9092?websocketEnabled=true + server: + websocket-prefix: /ws diff --git a/demo/demo-etcd/consumer/src/main/resources/log4j2.xml b/demo/demo-etcd/consumer/src/main/resources/log4j2.xml new file mode 100644 index 0000000000..c51f7ad503 --- /dev/null +++ b/demo/demo-etcd/consumer/src/main/resources/log4j2.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + diff --git a/demo/demo-etcd/gateway/pom.xml b/demo/demo-etcd/gateway/pom.xml new file mode 100644 index 0000000000..b2eb1e51df --- /dev/null +++ b/demo/demo-etcd/gateway/pom.xml @@ -0,0 +1,91 @@ + + + + 4.0.0 + + + org.apache.servicecomb.demo + demo-etcd + 3.3.0-SNAPSHOT + + + etcd-gateway + Java Chassis::Demo::Etcd::GATEWAY + jar + + + + org.apache.servicecomb + java-chassis-spring-boot-starter-standalone + + + org.apache.servicecomb + edge-core + + + com.google.protobuf + protobuf-java + 3.25.3 + runtime + + + org.apache.servicecomb + registry-etcd + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + docker + + + + io.fabric8 + docker-maven-plugin + + + org.commonjava.maven.plugins + directory-maven-plugin + + + com.github.odavid.maven.plugins + mixin-maven-plugin + + + + org.apache.servicecomb.demo + docker-build-config + ${project.version} + + + + + + + + + diff --git a/demo/demo-etcd/gateway/src/main/java/org/apache/servicecomb/samples/GatewayApplication.java b/demo/demo-etcd/gateway/src/main/java/org/apache/servicecomb/samples/GatewayApplication.java new file mode 100644 index 0000000000..7d58caafd9 --- /dev/null +++ b/demo/demo-etcd/gateway/src/main/java/org/apache/servicecomb/samples/GatewayApplication.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.apache.servicecomb.samples; + +import org.springframework.boot.WebApplicationType; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; + +@SpringBootApplication +public class GatewayApplication { + public static void main(String[] args) throws Exception { + try { + new SpringApplicationBuilder().web(WebApplicationType.NONE).sources(GatewayApplication.class).run(args); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/demo/demo-etcd/gateway/src/main/resources/application.yml b/demo/demo-etcd/gateway/src/main/resources/application.yml new file mode 100644 index 0000000000..f526eb3de7 --- /dev/null +++ b/demo/demo-etcd/gateway/src/main/resources/application.yml @@ -0,0 +1,53 @@ +# +## --------------------------------------------------------------------------- +## Licensed to the Apache Software Foundation (ASF) under one or more +## contributor license agreements. See the NOTICE file distributed with +## this work for additional information regarding copyright ownership. +## The ASF licenses this file to You under the Apache License, Version 2.0 +## (the "License"); you may not use this file except in compliance with +## the License. You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## 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. +## --------------------------------------------------------------------------- +servicecomb: + service: + application: demo-etcd + version: 0.0.1 + name: gateway + registry: + etcd: + enabled: true + connectString: http://127.0.0.1:2379 + + rest: + address: 0.0.0.0:9090?websocketEnabled=true + server: + websocket-prefix: /ws + + http: + dispatcher: + edge: + default: + enabled: false + url: + enabled: true + pattern: /(.*) + mappings: + consumer: + prefixSegmentCount: 0 + path: "/.*" + microserviceName: consumer + versionRule: 0.0.0+ + websocket: + mappings: + consumer: + prefixSegmentCount: 0 + path: "/ws/.*" + microserviceName: consumer + versionRule: 0.0.0+ diff --git a/demo/demo-etcd/gateway/src/main/resources/log4j2.xml b/demo/demo-etcd/gateway/src/main/resources/log4j2.xml new file mode 100644 index 0000000000..c51f7ad503 --- /dev/null +++ b/demo/demo-etcd/gateway/src/main/resources/log4j2.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + diff --git a/demo/demo-etcd/pom.xml b/demo/demo-etcd/pom.xml new file mode 100644 index 0000000000..bda7b06fe8 --- /dev/null +++ b/demo/demo-etcd/pom.xml @@ -0,0 +1,61 @@ + + + + + 4.0.0 + + + org.apache.servicecomb.demo + demo-parent + 3.3.0-SNAPSHOT + + demo-etcd + Java Chassis::Demo::Etcd + pom + + + org.apache.servicecomb.demo + demo-schema + + + org.apache.servicecomb + solution-basic + + + org.apache.logging.log4j + log4j-slf4j-impl + + + org.apache.logging.log4j + log4j-core + + + org.apache.logging.log4j + log4j-api + + + + + provider + consumer + gateway + test-client + + + diff --git a/demo/demo-etcd/provider/pom.xml b/demo/demo-etcd/provider/pom.xml new file mode 100644 index 0000000000..c1413de54c --- /dev/null +++ b/demo/demo-etcd/provider/pom.xml @@ -0,0 +1,97 @@ + + + + + 4.0.0 + + + org.apache.servicecomb.demo + demo-etcd + 3.3.0-SNAPSHOT + + + etcd-provider + Java Chassis::Demo::Etcd::PROVIDER + jar + + + + + + + org.apache.servicecomb + java-chassis-spring-boot-starter-standalone + + + com.google.protobuf + protobuf-java + 3.25.3 + runtime + + + org.apache.servicecomb + registry-etcd + + + + io.reactivex.rxjava3 + rxjava + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + docker + + + + io.fabric8 + docker-maven-plugin + + + org.commonjava.maven.plugins + directory-maven-plugin + + + com.github.odavid.maven.plugins + mixin-maven-plugin + + + + org.apache.servicecomb.demo + docker-build-config + ${project.version} + + + + + + + + + diff --git a/demo/demo-etcd/provider/src/main/java/org/apache/servicecomb/samples/EtcdProviderApplication.java b/demo/demo-etcd/provider/src/main/java/org/apache/servicecomb/samples/EtcdProviderApplication.java new file mode 100644 index 0000000000..388b962dc6 --- /dev/null +++ b/demo/demo-etcd/provider/src/main/java/org/apache/servicecomb/samples/EtcdProviderApplication.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.apache.servicecomb.samples; + +import org.springframework.boot.WebApplicationType; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; + +@SpringBootApplication +public class EtcdProviderApplication { + public static void main(String[] args) throws Exception { + try { + new SpringApplicationBuilder().web(WebApplicationType.NONE).sources(EtcdProviderApplication.class).run(args); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/demo/demo-etcd/provider/src/main/java/org/apache/servicecomb/samples/HeaderParamWithListSchema.java b/demo/demo-etcd/provider/src/main/java/org/apache/servicecomb/samples/HeaderParamWithListSchema.java new file mode 100644 index 0000000000..8a773f91c7 --- /dev/null +++ b/demo/demo-etcd/provider/src/main/java/org/apache/servicecomb/samples/HeaderParamWithListSchema.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.apache.servicecomb.samples; + +import java.util.List; + +import org.apache.servicecomb.demo.api.IHeaderParamWithListSchemaSpringMvc; +import org.apache.servicecomb.provider.rest.common.RestSchema; + +@RestSchema(schemaId = "HeaderParamWithListSchema", schemaInterface = IHeaderParamWithListSchemaSpringMvc.class) +public class HeaderParamWithListSchema implements IHeaderParamWithListSchemaSpringMvc { + @Override + public String headerListDefault(List headerList) { + return headerList == null ? "null" : headerList.size() + ":" + headerList; + } + + @Override + public String headerListCSV(List headerList) { + return headerList == null ? "null" : headerList.size() + ":" + headerList; + } + + @Override + public String headerListMULTI(List headerList) { + return headerList == null ? "null" : headerList.size() + ":" + headerList; + } + + @Override + public String headerListSSV(List headerList) { + return headerList == null ? "null" : headerList.size() + ":" + headerList; + } + + @Override + public String headerListPIPES(List headerList) { + return headerList == null ? "null" : headerList.size() + ":" + headerList; + } +} diff --git a/demo/demo-etcd/provider/src/main/java/org/apache/servicecomb/samples/ProviderController.java b/demo/demo-etcd/provider/src/main/java/org/apache/servicecomb/samples/ProviderController.java new file mode 100644 index 0000000000..8fc333cee1 --- /dev/null +++ b/demo/demo-etcd/provider/src/main/java/org/apache/servicecomb/samples/ProviderController.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.apache.servicecomb.samples; + +import org.apache.servicecomb.provider.rest.common.RestSchema; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; + +@RestSchema(schemaId = "ProviderController") +@RequestMapping(path = "/") +public class ProviderController implements InitializingBean { + private Environment environment; + + @Autowired + public void setEnvironment(Environment environment) { + this.environment = environment; + } + + // a very simple service to echo the request parameter + @GetMapping("/sayHello") + public String sayHello(@RequestParam("name") String name) { +// return "Hello " + environment.getProperty("servicecomb.rest.address"); + return "Hello " + name; + } + + @GetMapping("/getConfig") + public String getConfig(@RequestParam("key") String key) { + return environment.getProperty(key); + } + + @Override + public void afterPropertiesSet() throws Exception { + } +} diff --git a/demo/demo-etcd/provider/src/main/java/org/apache/servicecomb/samples/ReactiveStreamController.java b/demo/demo-etcd/provider/src/main/java/org/apache/servicecomb/samples/ReactiveStreamController.java new file mode 100644 index 0000000000..8108d15fd4 --- /dev/null +++ b/demo/demo-etcd/provider/src/main/java/org/apache/servicecomb/samples/ReactiveStreamController.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.apache.servicecomb.samples; + + +import java.util.concurrent.TimeUnit; + +import org.apache.servicecomb.core.CoreConst; +import org.apache.servicecomb.core.annotation.Transport; +import org.apache.servicecomb.provider.rest.common.RestSchema; +import org.reactivestreams.Publisher; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +import io.reactivex.rxjava3.core.Flowable; + +@RestSchema(schemaId = "ReactiveStreamController") +@RequestMapping(path = "/") +@Transport(name = CoreConst.RESTFUL) +public class ReactiveStreamController { + public static class Model { + private String name; + + private int age; + + public Model() { + + } + + public Model(String name, int age) { + this.name = name; + this.age = age; + } + + public int getAge() { + return age; + } + + public Model setAge(int age) { + this.age = age; + return this; + } + + public String getName() { + return name; + } + + public Model setName(String name) { + this.name = name; + return this; + } + } + + @GetMapping("/sseString") + public Publisher sseString() { + return Flowable.fromArray("a", "b", "c"); + } + + @GetMapping("/sseModel") + public Publisher sseModel() { + return Flowable.intervalRange(0, 5, 0, 1, TimeUnit.SECONDS) + .map(item -> new Model("jack", item.intValue())); + } +} diff --git a/demo/demo-etcd/provider/src/main/java/org/apache/servicecomb/samples/WebsocketController.java b/demo/demo-etcd/provider/src/main/java/org/apache/servicecomb/samples/WebsocketController.java new file mode 100644 index 0000000000..5f4f9719ca --- /dev/null +++ b/demo/demo-etcd/provider/src/main/java/org/apache/servicecomb/samples/WebsocketController.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.apache.servicecomb.samples; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.servicecomb.core.CoreConst; +import org.apache.servicecomb.core.annotation.Transport; +import org.apache.servicecomb.provider.rest.common.RestSchema; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +import io.vertx.core.http.ServerWebSocket; + +@RestSchema(schemaId = "WebsocketController") +@RequestMapping(path = "/ws") +public class WebsocketController { + @PostMapping("/websocket") + @Transport(name = CoreConst.WEBSOCKET) + public void websocket(ServerWebSocket serverWebsocket) { + // Client may have not registered message handler, and messages sent may get lost. + // So we sleep for a while to send message. + AtomicInteger receiveCount = new AtomicInteger(0); + serverWebsocket.textMessageHandler(s -> { + receiveCount.getAndIncrement(); + }); + serverWebsocket.closeHandler((v) -> System.out.println("closed")); + + new Thread(() -> { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + serverWebsocket.writeTextMessage("hello", r -> { + }); + + for (int i = 0; i < 5; i++) { + serverWebsocket.writeTextMessage("hello " + i, r -> { + }); + try { + Thread.sleep(500); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + serverWebsocket.writeTextMessage("total " + receiveCount.get()); + serverWebsocket.close(); + }).start(); + } +} diff --git a/demo/demo-etcd/provider/src/main/resources/application.yml b/demo/demo-etcd/provider/src/main/resources/application.yml new file mode 100644 index 0000000000..75b6dee9ea --- /dev/null +++ b/demo/demo-etcd/provider/src/main/resources/application.yml @@ -0,0 +1,44 @@ +# +## --------------------------------------------------------------------------- +## Licensed to the Apache Software Foundation (ASF) under one or more +## contributor license agreements. See the NOTICE file distributed with +## this work for additional information regarding copyright ownership. +## The ASF licenses this file to You under the Apache License, Version 2.0 +## (the "License"); you may not use this file except in compliance with +## the License. You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## 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. +## --------------------------------------------------------------------------- +# spring boot configurations +servicecomb: + service: + application: demo-etcd + version: 0.0.1 + name: provider + properties: + group: green + registry: + etcd: + connectString: http://127.0.0.1:2379 + + rest: + address: 0.0.0.0:9094?websocketEnabled=true + server: + websocket-prefix: /ws + + cors: + enabled: true + origin: "*" + allowCredentials: false + allowedMethod: "*" + maxAge: 3600 + +key1: 1 +key2: 3 +key3: 5 diff --git a/demo/demo-etcd/provider/src/main/resources/log4j2.xml b/demo/demo-etcd/provider/src/main/resources/log4j2.xml new file mode 100644 index 0000000000..c51f7ad503 --- /dev/null +++ b/demo/demo-etcd/provider/src/main/resources/log4j2.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + diff --git a/demo/demo-etcd/test-client/pom.xml b/demo/demo-etcd/test-client/pom.xml new file mode 100644 index 0000000000..0c050adcb8 --- /dev/null +++ b/demo/demo-etcd/test-client/pom.xml @@ -0,0 +1,213 @@ + + + + + 4.0.0 + + + org.apache.servicecomb.demo + demo-etcd + 3.3.0-SNAPSHOT + + + etcd-test-client + Java Chassis::Demo::Etcd::TEST-CLIENT + jar + + + + + + + org.apache.servicecomb + java-chassis-spring-boot-starter-standalone + + + org.apache.servicecomb.demo + demo-schema + + + org.apache.servicecomb + registry-local + + + + + + docker + + + + + io.fabric8 + docker-maven-plugin + + + + bitnami/etcd:latest + etcd + + alias + + etcdserver + + + 2379 + + + + + + etcd.port:2379 + + + yes + + + + + etcd-provider:${project.version} + etcd-provider + + alias + + + -Dservicecomb.registry.etcd.connectString=http://etcd:2379 + -Dservicecomb.config.etcd.connectString=http://etcd:2379 + + /maven/maven/etcd-provider-${project.version}.jar + + + etcd:etcd + + + ServiceComb is ready + + + 9094 + + + + + + 9094:9094 + + + + + etcd-consumer:${project.version} + etcd-consumer + + alias + + + -Dservicecomb.registry.etcd.connectString=http://etcd:2379 + + /maven/maven/etcd-consumer-${project.version}.jar + + + etcd:etcd + + + ServiceComb is ready + + + 9092 + + + + + + 9092:9092 + + + + + etcd-gateway:${project.version} + etcd-gateway + + alias + + + -Dservicecomb.registry.etcd.connectString=http://etcd:2379 + + /maven/maven/etcd-gateway-${project.version}.jar + + + etcd:etcd + + + ServiceComb is ready + + + 9090 + + + + + + 9090:9090 + + + + + + + + start + pre-integration-test + + start + + + + stop + post-integration-test + + stop + + + + + + + + + + io.fabric8 + docker-maven-plugin + + + com.github.odavid.maven.plugins + mixin-maven-plugin + + + + org.apache.servicecomb.demo + docker-run-config + ${project.version} + + + + + + + + + diff --git a/demo/demo-etcd/test-client/src/main/java/org/apache/servicecomb/samples/Config.java b/demo/demo-etcd/test-client/src/main/java/org/apache/servicecomb/samples/Config.java new file mode 100644 index 0000000000..2e9105cdd4 --- /dev/null +++ b/demo/demo-etcd/test-client/src/main/java/org/apache/servicecomb/samples/Config.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.apache.servicecomb.samples; + +public interface Config { + String GATEWAY_URL = "http://localhost:9090"; +} diff --git a/demo/demo-etcd/test-client/src/main/java/org/apache/servicecomb/samples/HeaderParamWithListSchemaIT.java b/demo/demo-etcd/test-client/src/main/java/org/apache/servicecomb/samples/HeaderParamWithListSchemaIT.java new file mode 100644 index 0000000000..1b129acc2c --- /dev/null +++ b/demo/demo-etcd/test-client/src/main/java/org/apache/servicecomb/samples/HeaderParamWithListSchemaIT.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.apache.servicecomb.samples; + +import org.apache.servicecomb.demo.CategorizedTestCase; +import org.apache.servicecomb.demo.TestMgr; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.stereotype.Component; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestOperations; +import org.springframework.web.client.RestTemplate; + +@Component +public class HeaderParamWithListSchemaIT implements CategorizedTestCase { + RestOperations template = new RestTemplate(); + + @Override + public void testRestTransport() throws Exception { + testHeaderListDefault(); + testHeaderListMulti(); + testHeaderListCSV(); + testHeaderListSSV(); + testHeaderListPipes(); + } + + // default to multi + private void testHeaderListDefault() { + MultiValueMap headers = new HttpHeaders(); + headers.add("headerList", "a"); + headers.add("headerList", "b"); + headers.add("headerList", "c"); + HttpEntity entity = new HttpEntity<>(headers); + String result = template + .exchange(Config.GATEWAY_URL + "/headerList/headerListDefault", HttpMethod.GET, entity, String.class).getBody(); + TestMgr.check("3:[a, b, c]", result); + } + + private void testHeaderListPipes() { + MultiValueMap headers = new HttpHeaders(); + headers.add("headerList", "a|b|c"); + HttpEntity entity = new HttpEntity<>(headers); + String result = template + .exchange(Config.GATEWAY_URL + "/headerList/headerListPIPES", HttpMethod.GET, entity, String.class).getBody(); + TestMgr.check("3:[a, b, c]", result); + } + + private void testHeaderListSSV() { + MultiValueMap headers = new HttpHeaders(); + headers.add("headerList", "a b c"); + HttpEntity entity = new HttpEntity<>(headers); + String result = template + .exchange(Config.GATEWAY_URL + "/headerList/headerListSSV", HttpMethod.GET, entity, String.class).getBody(); + TestMgr.check("3:[a, b, c]", result); + } + + private void testHeaderListCSV() { + MultiValueMap headers = new HttpHeaders(); + headers.add("headerList", "a,b,c"); + HttpEntity entity = new HttpEntity<>(headers); + String result = template + .exchange(Config.GATEWAY_URL + "/headerList/headerListCSV", HttpMethod.GET, entity, String.class).getBody(); + TestMgr.check("3:[a, b, c]", result); + + headers.add("headerList", "a, b, c"); + entity = new HttpEntity<>(headers); + result = template + .exchange(Config.GATEWAY_URL + "/headerList/headerListCSV", HttpMethod.GET, entity, String.class).getBody(); + TestMgr.check("3:[a, b, c]", result); + } + + private void testHeaderListMulti() { + MultiValueMap headers = new HttpHeaders(); + headers.add("headerList", "a"); + headers.add("headerList", "b"); + headers.add("headerList", "c"); + HttpEntity entity = new HttpEntity<>(headers); + String result = template + .exchange(Config.GATEWAY_URL + "/headerList/headerListMULTI", HttpMethod.GET, entity, String.class).getBody(); + TestMgr.check("3:[a, b, c]", result); + } +} diff --git a/demo/demo-etcd/test-client/src/main/java/org/apache/servicecomb/samples/HelloWorldIT.java b/demo/demo-etcd/test-client/src/main/java/org/apache/servicecomb/samples/HelloWorldIT.java new file mode 100644 index 0000000000..97e883fb45 --- /dev/null +++ b/demo/demo-etcd/test-client/src/main/java/org/apache/servicecomb/samples/HelloWorldIT.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.apache.servicecomb.samples; + +import org.apache.servicecomb.demo.CategorizedTestCase; +import org.apache.servicecomb.demo.TestMgr; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestOperations; +import org.springframework.web.client.RestTemplate; + +@Component +public class HelloWorldIT implements CategorizedTestCase { + RestOperations template = new RestTemplate(); + + @Override + public void testRestTransport() throws Exception { + testHelloWorld(); + testGetConfig(); + } + + private void testGetConfig() { + String result = template + .getForObject(Config.GATEWAY_URL + "/getConfig?key=key1", String.class); + TestMgr.check("1", result); + result = template + .getForObject(Config.GATEWAY_URL + "/getConfig?key=key2", String.class); + TestMgr.check("3", result); + result = template + .getForObject(Config.GATEWAY_URL + "/getConfig?key=key3", String.class); + TestMgr.check("5", result); + } + + private void testHelloWorld() { + String result = template + .getForObject(Config.GATEWAY_URL + "/sayHello?name=World", String.class); + TestMgr.check("Hello World", result); + + // test trace id added + MultiValueMap headers = new HttpHeaders(); + headers.add("X-B3-TraceId", "81de2eb7691c2bbb"); + HttpEntity entity = new HttpEntity(headers); + ResponseEntity response = + template.exchange(Config.GATEWAY_URL + "/sayHello?name=World", HttpMethod.GET, entity, String.class); + TestMgr.check(1, response.getHeaders().get("X-B3-TraceId").size()); + TestMgr.check("81de2eb7691c2bbb", response.getHeaders().getFirst("X-B3-TraceId")); + TestMgr.check("Hello World", response.getBody()); + } +} diff --git a/demo/demo-etcd/test-client/src/main/java/org/apache/servicecomb/samples/ReactiveStreamIT.java b/demo/demo-etcd/test-client/src/main/java/org/apache/servicecomb/samples/ReactiveStreamIT.java new file mode 100644 index 0000000000..488a6cf712 --- /dev/null +++ b/demo/demo-etcd/test-client/src/main/java/org/apache/servicecomb/samples/ReactiveStreamIT.java @@ -0,0 +1,119 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.apache.servicecomb.samples; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.apache.servicecomb.demo.CategorizedTestCase; +import org.apache.servicecomb.demo.TestMgr; +import org.apache.servicecomb.samples.ThirdSvcConfiguration.ReactiveStreamClient; +import org.apache.servicecomb.samples.ThirdSvcConfiguration.ReactiveStreamClient.Model; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +@Component +public class ReactiveStreamIT implements CategorizedTestCase { + @Autowired + @Qualifier("reactiveStreamProvider") + ReactiveStreamClient reactiveStreamProvider; + + @Autowired + @Qualifier("reactiveStreamGateway") + ReactiveStreamClient reactiveStreamGateway; + + @Override + public void testRestTransport() throws Exception { + testSseString(reactiveStreamProvider); + testSseModel(reactiveStreamProvider); + testSseString(reactiveStreamGateway); + testSseModel(reactiveStreamGateway); + } + + private void testSseModel(ReactiveStreamClient client) throws Exception { + Publisher result = client.sseModel(); + StringBuilder buffer = new StringBuilder(); + CountDownLatch countDownLatch = new CountDownLatch(1); + result.subscribe(new Subscriber<>() { + Subscription subscription; + + @Override + public void onSubscribe(Subscription s) { + subscription = s; + subscription.request(1); + } + + @Override + public void onNext(Model s) { + buffer.append(s.getName()).append(s.getAge()); + subscription.request(1); + } + + @Override + public void onError(Throwable t) { + subscription.cancel(); + countDownLatch.countDown(); + } + + @Override + public void onComplete() { + countDownLatch.countDown(); + } + }); + countDownLatch.await(10, TimeUnit.SECONDS); + TestMgr.check("jack0jack1jack2jack3jack4", buffer.toString()); + } + + private void testSseString(ReactiveStreamClient client) throws Exception { + Publisher result = client.sseString(); + StringBuilder buffer = new StringBuilder(); + CountDownLatch countDownLatch = new CountDownLatch(1); + result.subscribe(new Subscriber<>() { + Subscription subscription; + + @Override + public void onSubscribe(Subscription s) { + subscription = s; + subscription.request(1); + } + + @Override + public void onNext(String s) { + buffer.append(s); + subscription.request(1); + } + + @Override + public void onError(Throwable t) { + subscription.cancel(); + countDownLatch.countDown(); + } + + @Override + public void onComplete() { + countDownLatch.countDown(); + } + }); + countDownLatch.await(10, TimeUnit.SECONDS); + TestMgr.check("abc", buffer.toString()); + } +} diff --git a/demo/demo-etcd/test-client/src/main/java/org/apache/servicecomb/samples/TestClientApplication.java b/demo/demo-etcd/test-client/src/main/java/org/apache/servicecomb/samples/TestClientApplication.java new file mode 100644 index 0000000000..26a2a491bb --- /dev/null +++ b/demo/demo-etcd/test-client/src/main/java/org/apache/servicecomb/samples/TestClientApplication.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.apache.servicecomb.samples; + +import org.apache.servicecomb.demo.CategorizedTestCaseRunner; +import org.apache.servicecomb.demo.TestMgr; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.WebApplicationType; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; + +@SpringBootApplication +public class TestClientApplication { + private static final Logger LOGGER = LoggerFactory.getLogger(TestClientApplication.class); + + public static void main(String[] args) throws Exception { + try { + new SpringApplicationBuilder().web(WebApplicationType.NONE).sources(TestClientApplication.class).run(args); + + run(); + } catch (Exception e) { + TestMgr.failed("test case run failed", e); + LOGGER.error("-------------- test failed -------------"); + LOGGER.error("", e); + LOGGER.error("-------------- test failed -------------"); + } + TestMgr.summary(); + } + + public static void run() throws Exception { + CategorizedTestCaseRunner.runCategorizedTestCase("consumer"); + } +} diff --git a/demo/demo-etcd/test-client/src/main/java/org/apache/servicecomb/samples/ThirdSvcConfiguration.java b/demo/demo-etcd/test-client/src/main/java/org/apache/servicecomb/samples/ThirdSvcConfiguration.java new file mode 100644 index 0000000000..3ee5db8c34 --- /dev/null +++ b/demo/demo-etcd/test-client/src/main/java/org/apache/servicecomb/samples/ThirdSvcConfiguration.java @@ -0,0 +1,125 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.apache.servicecomb.samples; + +import java.util.List; + +import org.apache.servicecomb.core.CoreConst; +import org.apache.servicecomb.core.annotation.Transport; +import org.apache.servicecomb.localregistry.RegistryBean; +import org.apache.servicecomb.localregistry.RegistryBean.Instance; +import org.apache.servicecomb.localregistry.RegistryBean.Instances; +import org.apache.servicecomb.provider.pojo.Invoker; +import org.reactivestreams.Publisher; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +import io.vertx.core.http.WebSocket; + +@Configuration +public class ThirdSvcConfiguration { + @RequestMapping(path = "/ws") + public interface WebsocketClient { + @PostMapping("/websocket") + @Transport(name = CoreConst.WEBSOCKET) + WebSocket websocket(); + } + + @RequestMapping(path = "/") + public interface ReactiveStreamClient { + class Model { + private String name; + + private int age; + + public Model() { + + } + + public Model(String name, int age) { + this.name = name; + this.age = age; + } + + public int getAge() { + return age; + } + + public Model setAge(int age) { + this.age = age; + return this; + } + + public String getName() { + return name; + } + + public Model setName(String name) { + this.name = name; + return this; + } + } + + @GetMapping("/sseString") + Publisher sseString(); + + @GetMapping("/sseModel") + Publisher sseModel(); + } + + @Bean + public RegistryBean providerServiceBean() { + return new RegistryBean() + .addSchemaInterface("ReactiveStreamController", ReactiveStreamClient.class) + .setAppId("demo-etcd") + .setServiceName("provider") + .setVersion("0.0.1") + .setInstances(new Instances().setInstances(List.of( + new Instance().setEndpoints(List.of("rest://localhost:9094"))))); + } + + @Bean + public RegistryBean gatewayServiceBean() { + return new RegistryBean() + .addSchemaInterface("ReactiveStreamController", ReactiveStreamClient.class) + .addSchemaInterface("WebsocketController", WebsocketClient.class) + .setAppId("demo-etcd") + .setServiceName("gateway") + .setVersion("0.0.1") + .setInstances(new Instances().setInstances(List.of( + new Instance().setEndpoints(List.of("rest://localhost:9090?websocketEnabled=true"))))); + } + + @Bean("reactiveStreamProvider") + public ReactiveStreamClient reactiveStreamProvider() { + return Invoker.createProxy("provider", "ReactiveStreamController", ReactiveStreamClient.class); + } + + @Bean("reactiveStreamGateway") + public ReactiveStreamClient reactiveStreamGateway() { + return Invoker.createProxy("gateway", "ReactiveStreamController", ReactiveStreamClient.class); + } + + @Bean + public WebsocketClient gatewayWebsocketClient() { + return Invoker.createProxy("gateway", "WebsocketController", WebsocketClient.class); + } +} diff --git a/demo/demo-etcd/test-client/src/main/java/org/apache/servicecomb/samples/WebsocketIT.java b/demo/demo-etcd/test-client/src/main/java/org/apache/servicecomb/samples/WebsocketIT.java new file mode 100644 index 0000000000..ea7a7f45b1 --- /dev/null +++ b/demo/demo-etcd/test-client/src/main/java/org/apache/servicecomb/samples/WebsocketIT.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.apache.servicecomb.samples; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.apache.servicecomb.demo.CategorizedTestCase; +import org.apache.servicecomb.demo.TestMgr; +import org.apache.servicecomb.samples.ThirdSvcConfiguration.WebsocketClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import io.vertx.core.http.WebSocket; + +@Component +public class WebsocketIT implements CategorizedTestCase { + @Autowired + private WebsocketClient websocketClient; + + @Override + public void testRestTransport() throws Exception { + StringBuffer sb = new StringBuffer(); + AtomicBoolean closed = new AtomicBoolean(false); + CountDownLatch latch = new CountDownLatch(1); + + WebSocket webSocket = websocketClient.websocket(); + webSocket.textMessageHandler(s -> { + sb.append(s); + sb.append(" "); + webSocket.writeTextMessage(s); + }); + webSocket.closeHandler(v -> { + closed.set(true); + latch.countDown(); + }); + latch.await(30, TimeUnit.SECONDS); + TestMgr.check(sb.toString(), "hello hello 0 hello 1 hello 2 hello 3 hello 4 total 6 "); + TestMgr.check(closed.get(), true); + } +} diff --git a/demo/demo-etcd/test-client/src/main/resources/application.yml b/demo/demo-etcd/test-client/src/main/resources/application.yml new file mode 100644 index 0000000000..396bebfd85 --- /dev/null +++ b/demo/demo-etcd/test-client/src/main/resources/application.yml @@ -0,0 +1,25 @@ +# +## --------------------------------------------------------------------------- +## Licensed to the Apache Software Foundation (ASF) under one or more +## contributor license agreements. See the NOTICE file distributed with +## this work for additional information regarding copyright ownership. +## The ASF licenses this file to You under the Apache License, Version 2.0 +## (the "License"); you may not use this file except in compliance with +## the License. You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## 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. +## --------------------------------------------------------------------------- +servicecomb: + service: + application: demo-etcd + name: test-client + version: 0.0.1 + + rest: + address: 0.0.0.0:9097 # should be same with server.port to use web container diff --git a/demo/demo-etcd/test-client/src/main/resources/log4j2.xml b/demo/demo-etcd/test-client/src/main/resources/log4j2.xml new file mode 100644 index 0000000000..c51f7ad503 --- /dev/null +++ b/demo/demo-etcd/test-client/src/main/resources/log4j2.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + diff --git a/demo/demo-etcd/test-client/src/test/java/org/apache/servicecomb/samples/EtcdIT.java b/demo/demo-etcd/test-client/src/test/java/org/apache/servicecomb/samples/EtcdIT.java new file mode 100644 index 0000000000..833feff87c --- /dev/null +++ b/demo/demo-etcd/test-client/src/test/java/org/apache/servicecomb/samples/EtcdIT.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.apache.servicecomb.samples; + +import org.apache.servicecomb.demo.TestMgr; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +@ExtendWith(SpringExtension.class) +@SpringBootTest(classes = TestClientApplication.class) +public class EtcdIT { + private static final Logger LOGGER = LoggerFactory.getLogger(EtcdIT.class); + + @BeforeEach + public void setUp() { + TestMgr.errors().clear(); + } + + @Test + public void clientGetsNoError() { + try { + TestClientApplication.run(); + } catch (Exception e) { + TestMgr.failed("test case run failed", e); + LOGGER.error("-------------- test failed -------------"); + LOGGER.error("", e); + LOGGER.error("-------------- test failed -------------"); + } + TestMgr.summary(); + Assertions.assertTrue(TestMgr.errors().isEmpty()); + } +} diff --git a/demo/pom.xml b/demo/pom.xml index bfdd595efa..fa88e1526b 100644 --- a/demo/pom.xml +++ b/demo/pom.xml @@ -56,6 +56,7 @@ demo-cse-v1 demo-cse-v2 demo-nacos + demo-etcd demo-zookeeper diff --git a/dependencies/bom/pom.xml b/dependencies/bom/pom.xml index e417ae4cf5..f2218783fb 100644 --- a/dependencies/bom/pom.xml +++ b/dependencies/bom/pom.xml @@ -278,6 +278,11 @@ registry-zookeeper ${project.version} + + org.apache.servicecomb + registry-etcd + ${project.version} + org.apache.servicecomb diff --git a/dependencies/default/pom.xml b/dependencies/default/pom.xml index 573afe7f7f..d9cf1966f0 100644 --- a/dependencies/default/pom.xml +++ b/dependencies/default/pom.xml @@ -91,6 +91,7 @@ 4.5.10 3.4.1 3.4.0 + 0.8.3 ${basedir}/../.. @@ -542,6 +543,12 @@ pom import + + + io.etcd + jetcd-core + ${jetcd-core.version} + diff --git a/service-registry/pom.xml b/service-registry/pom.xml index 0fb0f34758..213f0b0f2d 100644 --- a/service-registry/pom.xml +++ b/service-registry/pom.xml @@ -37,5 +37,6 @@ registry-zero-config registry-nacos registry-zookeeper + registry-etcd diff --git a/service-registry/registry-etcd/pom.xml b/service-registry/registry-etcd/pom.xml new file mode 100644 index 0000000000..cf48811d34 --- /dev/null +++ b/service-registry/registry-etcd/pom.xml @@ -0,0 +1,52 @@ + + + + + + org.apache.servicecomb + service-registry-parent + 3.3.0-SNAPSHOT + + 4.0.0 + + registry-etcd + Java Chassis::Service Registry::Etcd + + + + + io.etcd + jetcd-core + + + org.apache.servicecomb + foundation-common + + + org.apache.servicecomb + foundation-registry + + + org.apache.servicecomb + java-chassis-core + + + + diff --git a/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/EtcdConfiguration.java b/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/EtcdConfiguration.java new file mode 100644 index 0000000000..a926c7bbc1 --- /dev/null +++ b/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/EtcdConfiguration.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.apache.servicecomb.registry.etcd; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class EtcdConfiguration { + @Bean + @ConfigurationProperties(prefix = EtcdConst.ETCD_REGISTRY_PREFIX) + public EtcdRegistryProperties etcdRegistryProperties() { + return new EtcdRegistryProperties(); + } + + @Bean + public EtcdDiscovery etcdDiscovery() { + return new EtcdDiscovery(); + } + + @Bean + public EtcdRegistration etcdRegistration() { + return new EtcdRegistration(); + } +} diff --git a/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/EtcdConst.java b/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/EtcdConst.java new file mode 100644 index 0000000000..03b28f0d2a --- /dev/null +++ b/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/EtcdConst.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.apache.servicecomb.registry.etcd; + +public class EtcdConst { + + public static final String ETCD_REGISTRY_NAME = "etcd-registry"; + + public static final String ETCD_DISCOVERY_ROOT = "/servicecomb/registry/%s"; + + public static final String ETCD_REGISTRY_PREFIX = "servicecomb.registry.etcd"; + + public static final String ETCD_DISCOVERY_ENABLED = ETCD_REGISTRY_PREFIX + ".%s.%s.enabled"; + + public static final String ETCD_DEFAULT_ENVIRONMENT = "production"; +} diff --git a/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/EtcdDiscovery.java b/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/EtcdDiscovery.java new file mode 100644 index 0000000000..3f58882698 --- /dev/null +++ b/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/EtcdDiscovery.java @@ -0,0 +1,197 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.apache.servicecomb.registry.etcd; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.StringUtils; +import org.apache.servicecomb.config.BootStrapProperties; +import org.apache.servicecomb.foundation.common.concurrent.ConcurrentHashMapEx; +import org.apache.servicecomb.foundation.common.utils.JsonUtils; +import org.apache.servicecomb.registry.api.Discovery; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; + +import com.google.common.collect.Lists; + +import io.etcd.jetcd.ByteSequence; +import io.etcd.jetcd.Client; +import io.etcd.jetcd.KeyValue; +import io.etcd.jetcd.Watch; +import io.etcd.jetcd.kv.GetResponse; +import io.etcd.jetcd.options.GetOption; +import io.etcd.jetcd.options.WatchOption; + +public class EtcdDiscovery implements Discovery { + + private Environment environment; + + private String basePath; + + private EtcdRegistryProperties etcdRegistryProperties; + + private Client client; + + private InstanceChangedListener instanceChangedListener; + + private static final Logger LOGGER = LoggerFactory.getLogger(EtcdDiscovery.class); + + private Map watchMap = new ConcurrentHashMapEx<>(); + + + @Autowired + @SuppressWarnings("unused") + public void setEnvironment(Environment environment) { + this.environment = environment; + } + + @Autowired + @SuppressWarnings("unused") + public void setEtcdRegistryProperties(EtcdRegistryProperties etcdRegistryProperties) { + this.etcdRegistryProperties = etcdRegistryProperties; + } + + @Override + public String name() { + return EtcdConst.ETCD_REGISTRY_NAME; + } + + @Override + public boolean enabled(String application, String serviceName) { + return environment.getProperty(String.format(EtcdConst.ETCD_DISCOVERY_ENABLED, application, serviceName), + boolean.class, true); + } + + @Override + public List findServiceInstances(String application, String serviceName) { + + String prefixPath = basePath + "/" + application + "/" + serviceName; + watchMap.computeIfAbsent(prefixPath, serName -> { + Watch watchClient = client.getWatchClient(); + try { + ByteSequence prefixByteSeq = ByteSequence.from(prefixPath, Charset.defaultCharset()); + watchClient.watch(prefixByteSeq, WatchOption.builder().withPrefix(prefixByteSeq).build(), + resp -> watchNode(application, serviceName, prefixPath)); + } catch (Exception e) { + LOGGER.error("Failed to add watch", e); + } + return watchClient; + }); + + List endpointKv = getValuesByPrefix(prefixPath); + return convertServiceInstanceList(endpointKv); + } + + private void watchNode(String application, String serviceName, String prefixPath) { + + CompletableFuture getFuture = client.getKVClient() + .get(ByteSequence.from(prefixPath, StandardCharsets.UTF_8), + GetOption.builder().withPrefix(ByteSequence.from(prefixPath, StandardCharsets.UTF_8)).build()); + getFuture.thenAcceptAsync(response -> { + List discoveryInstanceList = convertServiceInstanceList(response.getKvs()); + instanceChangedListener.onInstanceChanged(name(), application, serviceName, discoveryInstanceList); + }).exceptionally(e -> { + LOGGER.error("watchNode error", e); + return null; + }); + } + + private List getValuesByPrefix(String prefix) { + + CompletableFuture getFuture = client.getKVClient() + .get(ByteSequence.from(prefix, StandardCharsets.UTF_8), + GetOption.builder().withPrefix(ByteSequence.from(prefix, StandardCharsets.UTF_8)).build()); + GetResponse response = MuteExceptionUtil.builder().withLog("get kv by prefix error") + .executeCompletableFuture(getFuture); + return response.getKvs(); + } + + private List convertServiceInstanceList(List keyValueList) { + + List list = Lists.newArrayListWithExpectedSize(keyValueList.size()); + for (KeyValue keyValue : keyValueList) { + EtcdDiscoveryInstance etcdDiscoveryInstance = getEtcdDiscoveryInstance(keyValue); + list.add(etcdDiscoveryInstance); + } + return list; + } + + private static EtcdDiscoveryInstance getEtcdDiscoveryInstance(KeyValue keyValue) { + String valueJson = new String(keyValue.getValue().getBytes(), Charset.defaultCharset()); + + EtcdInstance etcdInstance = MuteExceptionUtil.builder() + .withLog("convert json value to obj from etcd failure, {}", valueJson) + .executeFunctionWithDoubleParam(JsonUtils::readValue, valueJson.getBytes(StandardCharsets.UTF_8), + EtcdInstance.class); + EtcdDiscoveryInstance etcdDiscoveryInstance = new EtcdDiscoveryInstance(etcdInstance); + return etcdDiscoveryInstance; + } + + @Override + public List findServices(String application) { + + String prefixPath = basePath + "/" + application; + List endpointKv = getValuesByPrefix(prefixPath); + return endpointKv.stream().map(kv -> kv.getKey().toString(StandardCharsets.UTF_8)).collect(Collectors.toList()); + } + + @Override + public void setInstanceChangedListener(InstanceChangedListener instanceChangedListener) { + this.instanceChangedListener = instanceChangedListener; + } + + @Override + public boolean enabled() { + return etcdRegistryProperties.isEnabled(); + } + + @Override + public void init() { + String env = BootStrapProperties.readServiceEnvironment(environment); + if (StringUtils.isEmpty(env)) { + env = EtcdConst.ETCD_DEFAULT_ENVIRONMENT; + } + basePath = String.format(EtcdConst.ETCD_DISCOVERY_ROOT, env); + } + + @Override + public void run() { + if (StringUtils.isEmpty(etcdRegistryProperties.getAuthenticationInfo())) { + this.client = Client.builder().endpoints(etcdRegistryProperties.getConnectString()).build(); + } else { + String[] authInfo = etcdRegistryProperties.getAuthenticationInfo().split(":"); + this.client = Client.builder().endpoints(etcdRegistryProperties.getConnectString()) + .user(ByteSequence.from(authInfo[0], Charset.defaultCharset())) + .password(ByteSequence.from(authInfo[1], Charset.defaultCharset())).build(); + } + } + + @Override + public void destroy() { + if (client != null) { + client.close(); + watchMap = null; + } + } +} diff --git a/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/EtcdDiscoveryInstance.java b/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/EtcdDiscoveryInstance.java new file mode 100644 index 0000000000..85f5ade014 --- /dev/null +++ b/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/EtcdDiscoveryInstance.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.apache.servicecomb.registry.etcd; + +import org.apache.servicecomb.registry.api.DiscoveryInstance; +import org.apache.servicecomb.registry.api.MicroserviceInstanceStatus; + +public class EtcdDiscoveryInstance extends EtcdInstance implements DiscoveryInstance { + + public EtcdDiscoveryInstance(EtcdInstance other) { + super(other); + } + + @Override + public MicroserviceInstanceStatus getStatus() { + return MicroserviceInstanceStatus.UP; + } + + @Override + public String getRegistryName() { + return EtcdConst.ETCD_REGISTRY_NAME; + } +} diff --git a/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/EtcdInstance.java b/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/EtcdInstance.java new file mode 100644 index 0000000000..19fbd8cadf --- /dev/null +++ b/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/EtcdInstance.java @@ -0,0 +1,208 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.apache.servicecomb.registry.etcd; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.servicecomb.registry.api.DataCenterInfo; +import org.apache.servicecomb.registry.api.MicroserviceInstance; + +public class EtcdInstance implements MicroserviceInstance { + private String serviceId; + + private String instanceId; + + private String environment; + + private String application; + + private String serviceName; + + private String alias; + + private String version; + + private String description; + + private DataCenterInfo dataCenterInfo; + + private List endpoints = new ArrayList<>(); + + private Map schemas = new HashMap<>(); + + private Map properties = new HashMap<>(); + + public EtcdInstance() { + + } + + public EtcdInstance(EtcdInstance other) { + this.serviceId = other.serviceId; + this.instanceId = other.instanceId; + this.environment = other.environment; + this.application = other.application; + this.serviceName = other.serviceName; + this.alias = other.alias; + this.version = other.version; + this.description = other.description; + this.dataCenterInfo = other.dataCenterInfo; + this.endpoints = other.endpoints; + this.schemas = other.schemas; + this.properties = other.properties; + } + + public void setServiceId(String serviceId) { + this.serviceId = serviceId; + } + + public void setInstanceId(String instanceId) { + this.instanceId = instanceId; + } + + public void setEnvironment(String environment) { + this.environment = environment; + } + + public void setApplication(String application) { + this.application = application; + } + + public void setServiceName(String serviceName) { + this.serviceName = serviceName; + } + + public void setAlias(String alias) { + this.alias = alias; + } + + public void setVersion(String version) { + this.version = version; + } + + public void setDescription(String description) { + this.description = description; + } + + public void setDataCenterInfo(DataCenterInfo dataCenterInfo) { + this.dataCenterInfo = dataCenterInfo; + } + + public void setEndpoints(List endpoints) { + this.endpoints = endpoints; + } + + public void setSchemas(Map schemas) { + this.schemas = schemas; + } + + public void setProperties(Map properties) { + this.properties = properties; + } + + @Override + public String getEnvironment() { + return this.environment; + } + + @Override + public String getApplication() { + return this.application; + } + + @Override + public String getServiceName() { + return this.serviceName; + } + + @Override + public String getAlias() { + return alias; + } + + @Override + public String getVersion() { + return version; + } + + @Override + public DataCenterInfo getDataCenterInfo() { + return dataCenterInfo; + } + + @Override + public String getDescription() { + return description; + } + + @Override + public Map getProperties() { + return properties; + } + + @Override + public Map getSchemas() { + return schemas; + } + + @Override + public List getEndpoints() { + return endpoints; + } + + public void addSchema(String schemaId, String content) { + this.schemas.put(schemaId, content); + } + + public void addEndpoint(String endpoint) { + this.endpoints.add(endpoint); + } + + public void addProperty(String key, String value) { + this.properties.put(key, value); + } + + @Override + public String getInstanceId() { + return instanceId; + } + + @Override + public String getServiceId() { + return serviceId; + } + + @Override + public String toString() { + return "EtcdInstance{" + + "serviceId='" + serviceId + '\'' + + ", instanceId='" + instanceId + '\'' + + ", environment='" + environment + '\'' + + ", application='" + application + '\'' + + ", serviceName='" + serviceName + '\'' + + ", alias='" + alias + '\'' + + ", version='" + version + '\'' + + ", description='" + description + '\'' + + ", dataCenterInfo=" + dataCenterInfo + + ", endpoints=" + endpoints + + ", schemas=" + schemas + + ", properties=" + properties + + '}'; + } +} diff --git a/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/EtcdRegistration.java b/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/EtcdRegistration.java new file mode 100644 index 0000000000..0fae8b94dc --- /dev/null +++ b/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/EtcdRegistration.java @@ -0,0 +1,199 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.apache.servicecomb.registry.etcd; + +import java.nio.charset.Charset; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.lang3.StringUtils; +import org.apache.servicecomb.config.BootStrapProperties; +import org.apache.servicecomb.config.DataCenterProperties; +import org.apache.servicecomb.foundation.common.utils.JsonUtils; +import org.apache.servicecomb.registry.RegistrationId; +import org.apache.servicecomb.registry.api.DataCenterInfo; +import org.apache.servicecomb.registry.api.MicroserviceInstanceStatus; +import org.apache.servicecomb.registry.api.Registration; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; + +import io.etcd.jetcd.ByteSequence; +import io.etcd.jetcd.Client; +import io.etcd.jetcd.KV; +import io.etcd.jetcd.Lease; +import io.etcd.jetcd.kv.PutResponse; +import io.etcd.jetcd.options.PutOption; + +public class EtcdRegistration implements Registration { + + private EtcdInstance etcdInstance; + + private Environment environment; + + private String basePath; + + private RegistrationId registrationId; + + private DataCenterProperties dataCenterProperties; + + private EtcdRegistryProperties etcdRegistryProperties; + + private Client client; + + private ScheduledExecutorService executorService; + + private String keyPath; + + private Long leaseId; + + @Autowired + @SuppressWarnings("unused") + public void setEnvironment(Environment environment) { + this.environment = environment; + } + + @Autowired + @SuppressWarnings("unused") + public void setEtcdRegistryProperties(EtcdRegistryProperties etcdRegistryProperties) { + this.etcdRegistryProperties = etcdRegistryProperties; + } + + @Autowired + public void setRegistrationId(RegistrationId registrationId) { + this.registrationId = registrationId; + } + + @Autowired + @SuppressWarnings("unused") + public void setDataCenterProperties(DataCenterProperties dataCenterProperties) { + this.dataCenterProperties = dataCenterProperties; + } + + @Override + public String name() { + return EtcdConst.ETCD_REGISTRY_NAME; + } + + @Override + public EtcdRegistrationInstance getMicroserviceInstance() { + return new EtcdRegistrationInstance(etcdInstance); + } + + @Override + public boolean updateMicroserviceInstanceStatus(MicroserviceInstanceStatus status) { + return true; + } + + @Override + public void addSchema(String schemaId, String content) { + if (etcdRegistryProperties.isEnableSwaggerRegistration()) { + etcdInstance.addSchema(schemaId, content); + } + } + + @Override + public void addEndpoint(String endpoint) { + etcdInstance.addEndpoint(endpoint); + } + + @Override + public void addProperty(String key, String value) { + etcdInstance.addProperty(key, value); + } + + @Override + public boolean enabled() { + return etcdRegistryProperties.isEnabled(); + } + + @Override + public void init() { + String env = BootStrapProperties.readServiceEnvironment(environment); + if (StringUtils.isEmpty(env)) { + env = EtcdConst.ETCD_DEFAULT_ENVIRONMENT; + } + basePath = String.format(EtcdConst.ETCD_DISCOVERY_ROOT, env); + etcdInstance = new EtcdInstance(); + etcdInstance.setInstanceId(registrationId.getInstanceId()); + etcdInstance.setEnvironment(env); + etcdInstance.setApplication(BootStrapProperties.readApplication(environment)); + etcdInstance.setServiceName(BootStrapProperties.readServiceName(environment)); + etcdInstance.setAlias(BootStrapProperties.readServiceAlias(environment)); + etcdInstance.setDescription(BootStrapProperties.readServiceDescription(environment)); + if (StringUtils.isNotEmpty(dataCenterProperties.getName())) { + DataCenterInfo dataCenterInfo = new DataCenterInfo(); + dataCenterInfo.setName(dataCenterProperties.getName()); + dataCenterInfo.setRegion(dataCenterProperties.getRegion()); + dataCenterInfo.setAvailableZone(dataCenterProperties.getAvailableZone()); + etcdInstance.setDataCenterInfo(dataCenterInfo); + } + etcdInstance.setProperties(BootStrapProperties.readServiceProperties(environment)); + etcdInstance.setVersion(BootStrapProperties.readServiceVersion(environment)); + } + + @Override + public void run() { + client = Client.builder().endpoints(etcdRegistryProperties.getConnectString()) + .build(); + keyPath = basePath + "/" + + BootStrapProperties.readApplication(environment) + "/" + + BootStrapProperties.readServiceName(environment) + "/" + + registrationId.getInstanceId(); + + String valueJson = MuteExceptionUtil.builder().withLog("to json, key:{}, value:{}", keyPath, etcdInstance) + .executeFunction(JsonUtils::writeValueAsString, etcdInstance); + register(ByteSequence.from(keyPath, Charset.defaultCharset()), + ByteSequence.from(valueJson, Charset.defaultCharset())); + } + + public void register(ByteSequence key, ByteSequence value) { + + Lease leaseClient = client.getLeaseClient(); + leaseId = MuteExceptionUtil.builder().withLog("get lease id, key:{}, value:{}", keyPath, etcdInstance) + .executeCompletableFuture(leaseClient.grant(60)).getID(); + KV kvClient = client.getKVClient(); + + PutOption putOption = PutOption.builder().withLeaseId(leaseId).build(); + CompletableFuture putResponse = kvClient.put(key, value, putOption); + putResponse.thenRun(() -> { + executorService = Executors.newSingleThreadScheduledExecutor(); + executorService.scheduleAtFixedRate( + () -> MuteExceptionUtil.builder().withLog("reRegister, {}, {}", keyPath, etcdInstance) + .executeFunction(leaseClient::keepAliveOnce, leaseId), 0, 5, TimeUnit.SECONDS); + }); + } + + private void unregister() { + // close job + executorService.shutdownNow(); + + // close etcd node + Lease leaseClient = client.getLeaseClient(); + leaseClient.revoke(leaseId); + client.getKVClient().delete(ByteSequence.from(keyPath, Charset.defaultCharset())); + } + + @Override + public void destroy() { + if (client != null) { + unregister(); + client.close(); + } + } +} diff --git a/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/EtcdRegistrationInstance.java b/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/EtcdRegistrationInstance.java new file mode 100644 index 0000000000..444fe86708 --- /dev/null +++ b/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/EtcdRegistrationInstance.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.apache.servicecomb.registry.etcd; + +import org.apache.servicecomb.registry.api.MicroserviceInstanceStatus; +import org.apache.servicecomb.registry.api.RegistrationInstance; + +public class EtcdRegistrationInstance extends EtcdInstance implements RegistrationInstance { + public EtcdRegistrationInstance(EtcdInstance instance) { + super(instance); + } + + @Override + public MicroserviceInstanceStatus getInitialStatus() { + return MicroserviceInstanceStatus.STARTING; + } + + @Override + public MicroserviceInstanceStatus getReadyStatus() { + return MicroserviceInstanceStatus.UP; + } +} diff --git a/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/EtcdRegistryProperties.java b/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/EtcdRegistryProperties.java new file mode 100644 index 0000000000..c4d61094bf --- /dev/null +++ b/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/EtcdRegistryProperties.java @@ -0,0 +1,99 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.apache.servicecomb.registry.etcd; + +public class EtcdRegistryProperties { + private boolean enabled = true; + + private boolean ephemeral = true; + + private String connectString = "http://127.0.0.1:2379"; + + private String authenticationSchema; + + private String authenticationInfo; + + private int connectionTimeoutMillis = 1000; + + private int sessionTimeoutMillis = 60000; + + private boolean enableSwaggerRegistration = false; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public boolean isEphemeral() { + return ephemeral; + } + + public void setEphemeral(boolean ephemeral) { + this.ephemeral = ephemeral; + } + + public String getConnectString() { + return connectString; + } + + public void setConnectString(String connectString) { + this.connectString = connectString; + } + + public int getConnectionTimeoutMillis() { + return connectionTimeoutMillis; + } + + public void setConnectionTimeoutMillis(int connectionTimeoutMillis) { + this.connectionTimeoutMillis = connectionTimeoutMillis; + } + + public int getSessionTimeoutMillis() { + return sessionTimeoutMillis; + } + + public void setSessionTimeoutMillis(int sessionTimeoutMillis) { + this.sessionTimeoutMillis = sessionTimeoutMillis; + } + + public boolean isEnableSwaggerRegistration() { + return enableSwaggerRegistration; + } + + public void setEnableSwaggerRegistration(boolean enableSwaggerRegistration) { + this.enableSwaggerRegistration = enableSwaggerRegistration; + } + + public String getAuthenticationSchema() { + return authenticationSchema; + } + + public void setAuthenticationSchema(String authenticationSchema) { + this.authenticationSchema = authenticationSchema; + } + + public String getAuthenticationInfo() { + return authenticationInfo; + } + + public void setAuthenticationInfo(String authenticationInfo) { + this.authenticationInfo = authenticationInfo; + } +} diff --git a/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/MuteExceptionUtil.java b/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/MuteExceptionUtil.java new file mode 100644 index 0000000000..83041cacfb --- /dev/null +++ b/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/MuteExceptionUtil.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.apache.servicecomb.registry.etcd; + +import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class MuteExceptionUtil { + + interface FunctionWithException { + R apply(T t) throws Exception; + } + + interface FunctionWithDoubleParam { + R apply(T1 t1, T2 t2) throws Exception; + } + + private static final Logger LOGGER = LoggerFactory.getLogger(MuteExceptionUtil.class); + + public static class MuteExceptionUtilBuilder { + + private String logMessage; + + private Object[] customMessageParams; + + public MuteExceptionUtilBuilder withLog(String message, Object... params) { + this.logMessage = message; + this.customMessageParams = params; + return this; + } + + private String getLogMessage(String defaultMessage) { + return logMessage != null ? logMessage : defaultMessage; + } + + // 执行带异常处理的Function + public R executeFunction(FunctionWithException function, T t) { + try { + return function.apply(t); + } catch (Exception e) { + LOGGER.error(getLogMessage("execute Function failure..."), customMessageParams, e); + return null; + } + } + + public T executeSupplier(Supplier supplier) { + try { + return supplier.get(); + } catch (Exception e) { + LOGGER.error(getLogMessage("execute Supplier failure..."), customMessageParams, e); + return null; + } + } + + public T executeCompletableFuture(CompletableFuture completableFuture) { + try { + return completableFuture.get(); + } catch (Exception e) { + LOGGER.error(getLogMessage("execute CompletableFuture failure..."), customMessageParams, e); + return null; + } + } + + public R executeFunctionWithDoubleParam(FunctionWithDoubleParam function, T1 t1, T2 t2) { + try { + return function.apply(t1, t2); + } catch (Exception e) { + LOGGER.error(getLogMessage("execute FunctionWithDoubleParam failure..."), customMessageParams, e); + return null; + } + } + } + + public static MuteExceptionUtilBuilder builder() { + return new MuteExceptionUtilBuilder(); + } +} diff --git a/service-registry/registry-etcd/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/service-registry/registry-etcd/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000000..999ffb6b04 --- /dev/null +++ b/service-registry/registry-etcd/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,18 @@ +## --------------------------------------------------------------------------- +## Licensed to the Apache Software Foundation (ASF) under one or more +## contributor license agreements. See the NOTICE file distributed with +## this work for additional information regarding copyright ownership. +## The ASF licenses this file to You under the Apache License, Version 2.0 +## (the "License"); you may not use this file except in compliance with +## the License. You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## 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. +## --------------------------------------------------------------------------- + +org.apache.servicecomb.registry.etcd.EtcdConfiguration