diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e1377e6..a8cfefc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,12 +12,11 @@ We recommend that developers following the rules [here](https://jeffkreeftmeijer ## Contributing -- Fork the repository. +- Fork the repository or create branches. - Checkout your branch like a feature or bugfix. - Make a pull request to the `develop` branch of this repository. - Wait for review and PR merged. --- -**Working on your first Pull Request?** You can learn how from this *free* series [How to Contribute to an Open Source Project on GitHub](https://kcd.im/pull-request) - +**Working on your first Pull Request?** You can learn how from this *free* series [How to Contribute to an Open Source Project on GitHub](https://kcd.im/pull-request) \ No newline at end of file diff --git a/README.md b/README.md index 3929230..b629cd8 100644 --- a/README.md +++ b/README.md @@ -47,14 +47,14 @@ AntChain Bridge 插件服务(PluginServer, PS)用于管理异构链插件、 mvn clean package ``` -产生的安装包在`ps-bootstrap/target/plugin-server-0.1.2-SNAPSHOT.tar.gz`。 +产生的安装包在`ps-bootstrap/target/plugin-server-x.x.x.tar.gz`。 ## 配置 -在获得安装包之后,执行解压缩操作,这里以`plugin-server-0.1.2-SNAPSHOT.tar.gz`为例。 +在获得安装包之后,执行解压缩操作,这里以`plugin-server-x.x.x.tar.gz`为例。 ``` -tar -zxf plugin-server-0.1.2-SNAPSHOT.tar.gz +tar -zxf plugin-server-x.x.x.tar.gz ``` 进入解压后的目录,可以看到: @@ -72,7 +72,7 @@ tree . ├── config │   └── application.yml └── lib -    └── ps-bootstrap-0.1.2-SNAPSHOT.jar +    └── ps-bootstrap-x.x.x.jar 3 directories, 7 files diff --git a/pom.xml b/pom.xml index 3a29ea9..33913be 100644 --- a/pom.xml +++ b/pom.xml @@ -14,11 +14,12 @@ ps-service ps-server ps-pluginmanager + ps-cli 8 - 0.1.2-SNAPSHOT + 0.2.0 2.13.1.RELEASE 2.0.59.Final 3.19.1 @@ -26,12 +27,19 @@ 1.42.2 1.3.5 4.13.2 - 0.1-SNAPSHOT + 0.1.1 + 3.3.0 + 3.0.17 + 1.5.0 + 5.8.10 + 2.0.21 + 3.8.0 + 1.7.28 com.alipay.antchain.bridge antchain-bridge-pluginserver - 0.1.2-SNAPSHOT + 0.2.0 antchain-bridge-pluginserver antchain-bridge-pluginserver @@ -103,25 +111,48 @@ org.pf4j pf4j - 3.8.0 + ${pf4j.version} - com.alipay.antchain.bridge antchain-bridge-plugin-lib ${antchain-bridge.sdk.version} - org.slf4j slf4j-api - 1.7.28 + ${slf4j.version} - org.slf4j slf4j-simple - 1.7.28 + ${slf4j.version} + + + org.jline + jline + ${jline.version} + + + org.codehaus.groovy + groovy-all + ${groovy.version} + pom + + + commons-cli + commons-cli + ${commons-cli.version} + + + cn.hutool + hutool-all + ${hutool.version} + + + com.alibaba + fastjson + ${fastjson.version} diff --git a/ps-bootstrap/pom.xml b/ps-bootstrap/pom.xml index 6ec7d41..1f829cc 100644 --- a/ps-bootstrap/pom.xml +++ b/ps-bootstrap/pom.xml @@ -6,7 +6,7 @@ com.alipay.antchain.bridge antchain-bridge-pluginserver - 0.1.2-SNAPSHOT + 0.2.0 ps-bootstrap diff --git a/ps-bootstrap/src/main/java/com/alipay/antchain/bridge/pluginserver/config/PluginManagerConfiguration.java b/ps-bootstrap/src/main/java/com/alipay/antchain/bridge/pluginserver/config/PluginManagerConfiguration.java index 4660e37..76715f4 100644 --- a/ps-bootstrap/src/main/java/com/alipay/antchain/bridge/pluginserver/config/PluginManagerConfiguration.java +++ b/ps-bootstrap/src/main/java/com/alipay/antchain/bridge/pluginserver/config/PluginManagerConfiguration.java @@ -17,6 +17,7 @@ package com.alipay.antchain.bridge.pluginserver.config; import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.util.StrUtil; import com.alipay.antchain.bridge.pluginserver.pluginmanager.IPluginManagerWrapper; import com.alipay.antchain.bridge.pluginserver.pluginmanager.PluginManagerWrapperImpl; import com.alipay.antchain.bridge.pluginserver.server.PluginManagementServiceImpl; @@ -24,11 +25,14 @@ import io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder; import lombok.extern.slf4j.Slf4j; import org.pf4j.ClassLoadingStrategy; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -43,6 +47,9 @@ public class PluginManagerConfiguration { @Value("${pluginserver.plugin.policy.classloader.resource.ban-with-prefix.APPLICATION}") private String[] resourceBannedPrefixOnAppLevel; + @Value("${pluginserver.managerserver.host}") + private String managementHost; + @Bean public IPluginManagerWrapper pluginManagerWrapper() { return new PluginManagerWrapperImpl( @@ -66,10 +73,14 @@ private Map> convertPathPrefixBannedMap private String pluginServerMgrPort; @Bean - public Server pluginMgrServer() throws IOException { + public Server pluginMgrServer(@Autowired PluginManagementServiceImpl pluginManagementService) throws IOException { log.info("Starting plugin managing server on port " + pluginServerMgrPort); - return NettyServerBuilder.forPort(Integer.parseInt(pluginServerMgrPort)) - .addService(new PluginManagementServiceImpl()) + return NettyServerBuilder.forAddress( + new InetSocketAddress( + StrUtil.isEmpty(managementHost) ? InetAddress.getLoopbackAddress() : InetAddress.getByName(managementHost), + Integer.parseInt(pluginServerMgrPort) + ) + ).addService(pluginManagementService) .build() .start(); } diff --git a/ps-bootstrap/src/main/resources/application.yml b/ps-bootstrap/src/main/resources/application.yml index b719be1..da0752c 100644 --- a/ps-bootstrap/src/main/resources/application.yml +++ b/ps-bootstrap/src/main/resources/application.yml @@ -28,9 +28,12 @@ pluginserver: # where to load the hetero-chain plugins repo: ./plugins policy: + # limit actions of the plugin classloader classloader: resource: ban-with-prefix: + # the plugin classloader will not read the resource file starting with the prefix below APPLICATION: "META-INF/services/io.grpc." managerserver: + host: localhost port: 9091 \ No newline at end of file diff --git a/ps-bootstrap/src/main/resources/scripts/start.sh b/ps-bootstrap/src/main/resources/scripts/start.sh index 59ee7d0..f451440 100755 --- a/ps-bootstrap/src/main/resources/scripts/start.sh +++ b/ps-bootstrap/src/main/resources/scripts/start.sh @@ -7,7 +7,9 @@ print_title log_info "start plugin-server now..." -java -jar -Dlogging.file.path=${CURR_DIR}/../log ${CURR_DIR}/../lib/ps-bootstrap-0.1.2-SNAPSHOT.jar --spring.config.location=file:${CURR_DIR}/../config/application.yml > /dev/null 2>&1 & +JAR_FILE=`ls ${CURR_DIR}/../lib/ | grep '.jar'` + +java -jar -Dlogging.file.path=${CURR_DIR}/../log ${CURR_DIR}/../lib/${JAR_FILE} --spring.config.location=file:${CURR_DIR}/../config/application.yml > /dev/null 2>&1 & if [ $? -ne 0 ]; then log_error "failed to start plugin-server" exit 1 diff --git a/ps-bootstrap/src/test/java/com/alipay/antchain/bridge/pluginserver/AntChainBridgePluginManagementApplicationTests.java b/ps-bootstrap/src/test/java/com/alipay/antchain/bridge/pluginserver/AntChainBridgePluginManagementApplicationTests.java index afe5385..044496b 100644 --- a/ps-bootstrap/src/test/java/com/alipay/antchain/bridge/pluginserver/AntChainBridgePluginManagementApplicationTests.java +++ b/ps-bootstrap/src/test/java/com/alipay/antchain/bridge/pluginserver/AntChainBridgePluginManagementApplicationTests.java @@ -22,6 +22,7 @@ import com.alipay.antchain.bridge.pluginserver.server.CrossChainServiceImpl; import com.alipay.antchain.bridge.pluginserver.server.PluginManagementServiceImpl; import com.alipay.antchain.bridge.pluginserver.server.ResponseBuilder; +import com.alipay.antchain.bridge.pluginserver.server.exception.ServerErrorCodeEnum; import com.alipay.antchain.bridge.pluginserver.service.CallBBCRequest; import com.alipay.antchain.bridge.pluginserver.service.CallBBCResponse; import com.alipay.antchain.bridge.pluginserver.service.Response; @@ -83,7 +84,7 @@ public void testManageLoadAndStartPluginsReq() { @Test @DirtiesContext - public void testManagePluginReq(){ + public void testManagePluginReq() { PluginManageRequest pluginManageRequest; // 1. load plugin @@ -163,7 +164,7 @@ public void testManagePluginReq(){ @Test @DirtiesContext - public void testPluginQueryReq(){ + public void testPluginQueryReq() { PluginManageRequest pluginManageRequest; // 1. All plugins in the default path are automatically loaded when the service starts @@ -174,7 +175,7 @@ public void testPluginQueryReq(){ mngResponseStreamObserver = Mockito.mock(StreamObserver.class); pluginManagementService.hasPlugins(hasPluginsRequest, mngResponseStreamObserver); Mockito.verify(mngResponseStreamObserver).onNext(ResponseBuilder.buildHasPluginsResp( - HasPluginsResp.newBuilder().putAllResults(new HashMap(){{ + HasPluginsResp.newBuilder().putAllResults(new HashMap() {{ put(DEFAULT_PRODUCT, true); put(TEST_PRODUCT, false); }}) @@ -214,13 +215,13 @@ public void testPluginQueryReq(){ mngResponseStreamObserver = Mockito.mock(StreamObserver.class); pluginManagementService.hasDomains(hasDomainsRequest, mngResponseStreamObserver); Mockito.verify(mngResponseStreamObserver).onNext(ResponseBuilder.buildHasDomainsResp( - HasDomainsResp.newBuilder().putAllResults(new HashMap(){{ + HasDomainsResp.newBuilder().putAllResults(new HashMap() {{ put(TEST_DOMAIN, false); }}) )); Mockito.verify(mngResponseStreamObserver).onCompleted(); - // 6. all doamin + // 6. all domain AllDomainsRequest allDomainsRequest = AllDomainsRequest.newBuilder().build(); mngResponseStreamObserver = Mockito.mock(StreamObserver.class); pluginManagementService.allDomains(allDomainsRequest, mngResponseStreamObserver); @@ -229,7 +230,7 @@ public void testPluginQueryReq(){ )); Mockito.verify(mngResponseStreamObserver).onCompleted(); - // 7. create service with test doamin + // 7. create service with test domain AbstractBBCContext mockCtx = AntChainBridgePluginServerApplicationTests.mockInitCtx(); CallBBCRequest callBBCRequest = CallBBCRequest.newBuilder() .setProduct(DEFAULT_PRODUCT) @@ -240,7 +241,7 @@ public void testPluginQueryReq(){ Mockito.verify(responseStreamObserver).onNext(ResponseBuilder.buildBBCSuccessResp(CallBBCResponse.newBuilder())); Mockito.verify(responseStreamObserver).onCompleted(); - // 8. all doamin + // 8. all domain allDomainsRequest = AllDomainsRequest.newBuilder().build(); mngResponseStreamObserver = Mockito.mock(StreamObserver.class); pluginManagementService.allDomains(allDomainsRequest, mngResponseStreamObserver); @@ -250,4 +251,50 @@ public void testPluginQueryReq(){ Mockito.verify(mngResponseStreamObserver).onCompleted(); } + @Test + @DirtiesContext + public void testRestartBBC() { + // 1. failed to restart a domain or product not exist + RestartBBCRequest request = RestartBBCRequest.newBuilder() + .setDomain("domain") + .setProduct("product") + .build(); + mngResponseStreamObserver = Mockito.mock(StreamObserver.class); + pluginManagementService.restartBBC(request, mngResponseStreamObserver); + Mockito.verify(mngResponseStreamObserver) + .onNext(ResponseBuilder.buildFailManageResp(ServerErrorCodeEnum.MANAGE_RESTART_BBC_ERROR, "product not found")); + Mockito.verify(mngResponseStreamObserver).onCompleted(); + + // 2. green case + PluginManageRequest pluginManageRequest = PluginManageRequest.newBuilder() + .setType(PluginManageRequest.Type.LOAD_PLUGIN) + .setPath(TEST_PLUGIN_PATH).build(); + mngResponseStreamObserver = Mockito.mock(StreamObserver.class); + pluginManagementService.managePlugin(pluginManageRequest, mngResponseStreamObserver); + pluginManageRequest = PluginManageRequest.newBuilder() + .setType(PluginManageRequest.Type.START_PLUGIN) + .setPath(TEST_PLUGIN_PATH).build(); + mngResponseStreamObserver = Mockito.mock(StreamObserver.class); + pluginManagementService.managePlugin(pluginManageRequest, mngResponseStreamObserver); + + responseStreamObserver = Mockito.mock(StreamObserver.class); + crossChainService.bbcCall( + CallBBCRequest.newBuilder() + .setDomain("domain") + .setProduct("testchain") + .setStartUpReq(StartUpRequest.newBuilder().setRawContext(ByteString.copyFromUtf8("{\"raw_conf\": \"\"}"))) + .build(), + responseStreamObserver + ); + + request = RestartBBCRequest.newBuilder() + .setDomain("domain") + .setProduct("testchain") + .build(); + mngResponseStreamObserver = Mockito.mock(StreamObserver.class); + pluginManagementService.restartBBC(request, mngResponseStreamObserver); + + Mockito.verify(mngResponseStreamObserver).onNext(ResponseBuilder.buildRestartBBCResp(RestartBBCResp.newBuilder())); + Mockito.verify(mngResponseStreamObserver).onCompleted(); + } } diff --git a/ps-bootstrap/src/test/resources/application.yml b/ps-bootstrap/src/test/resources/application.yml index 158fbd7..81393e9 100644 --- a/ps-bootstrap/src/test/resources/application.yml +++ b/ps-bootstrap/src/test/resources/application.yml @@ -32,4 +32,5 @@ pluginserver: ban-with-prefix: APPLICATION: "META-INF/services/io.grpc." managerserver: + host: 0.0.0.0 port: 9091 \ No newline at end of file diff --git a/ps-cli/.gitignore b/ps-cli/.gitignore new file mode 100644 index 0000000..5ff6309 --- /dev/null +++ b/ps-cli/.gitignore @@ -0,0 +1,38 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/ps-cli/README.md b/ps-cli/README.md new file mode 100644 index 0000000..502c22a --- /dev/null +++ b/ps-cli/README.md @@ -0,0 +1,319 @@ +
+ am logo +

AntChain Bridge Plugin Server CLI

+
+ +## 介绍 + +为了帮助管理插件服务,我们提供一个命令行工具(Command-Line Interface, CLI),帮助进行一些插件服务的运维和管理工作,比如更新插件的之后,可以通过CLI重新加载插件。 + +## 快速开始 + +### 编译 + +在编译插件服务的时候,会同时编译CLI。在插件服务项目的根目录下,执行: + +``` +mvn clean package +``` + +编译之后,压缩包在路径`ps-cli/target/`之下。 + +当然,也可以在[release](https://github.com/AntChainOpenLab/AntChainBridgePluginServer/releases)页面下载安装包。 + +### 运行 + +首先,解压安装包,这里以`plugin-server-cli-0.2.0.tar.gz`为例。 + +``` +tar -zxf plugin-server-cli-0.2.0.tar.gz +``` + +然后,进入文件夹`plugin-server-cli`,文件结构如下: + +``` +. +├── README.md +├── bin +│   └── start.sh +└── lib + └── ps-cli-0.2.0.jar + +2 directories, 3 files +``` + +通过运行`bin/start.sh -h`可以打印帮助信息,查看脚本使用方式: + +``` +╰─± bin/start.sh -h + + start.sh — Start the CLI tool to manage your plugin server + + Usage: + start.sh + + Examples: + 1. print help info: + start.sh -h + 2. identify the port number to start CLI + start.sh -p 9091 + 3. identify the server IP to start CLI + start.sh -H 0.0.0.0 + + Options: + -h print help info + -p identify the port number to start CLI, default 9091 + -H identify the server IP to start CLI, default 0.0.0.0 + +``` + +运行`bin/start.sh`以启动CLI,按下Tab键可以补全命令。 + +### 使用 + +#### 重新加载插件 + +场景:让插件服务,重新加载某个类型的插件。如果你修改了插件逻辑并重新编译,将插件的Jar包拷贝并覆盖原有文件,运行命令,插件服务会重新加载新的插件文件。 + +命令:`manage.reloadPlugin(String product)` + +参数:参数`product`为插件中设置的区块链类型,比如我们提供的插件[demo](https://github.com/AntChainOpenLab/AntChainBridgePluginSDK/blob/df28f973a6f0ebdf204b86c2e49aa1be8f8d0c0c/pluginset/ethereum/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/ethereum/EthereumBBCService.java#L57)中的`simple-ethereum`。 + +``` + ___ __ ______ __ _ ____ _ __ + / | ____ / /_ / ____// /_ ____ _ (_)____ / __ ) _____ (_)____/ /____ _ ___ + / /| | / __ \ / __// / / __ \ / __ `// // __ \ / __ |/ ___// // __ // __ `// _ \ + / ___ | / / / // /_ / /___ / / / // /_/ // // / / / / /_/ // / / // /_/ // /_/ // __/ +/_/ |_|/_/ /_/ \__/ \____//_/ /_/ \__,_//_//_/ /_/ /_____//_/ /_/ \__,_/ \__, / \___/ + /____/ + PLUGIN SERVER CLI 0.2.0 + +>>> type help to see all commands... +ps> +ps> manage.reloadPlugin("simple-ethereum") +``` + +#### 在新路径重新加载插件 + +场景:让插件服务,在一个新路径重新加载某个类型的插件。如果你修改了插件逻辑并重新编译,指定新的插件文件路径,运行命令,插件服务会重新加载新的插件文件。 + +命令:`manage.reloadPluginInNewPath(String product, String path)` + +参数:参数`product`为插件中设置的区块链类型;参数`path`指定新的插件文件路径; + +``` + ___ __ ______ __ _ ____ _ __ + / | ____ / /_ / ____// /_ ____ _ (_)____ / __ ) _____ (_)____/ /____ _ ___ + / /| | / __ \ / __// / / __ \ / __ `// // __ \ / __ |/ ___// // __ // __ `// _ \ + / ___ | / / / // /_ / /___ / / / // /_/ // // / / / / /_/ // / / // /_/ // /_/ // __/ +/_/ |_|/_/ /_/ \__/ \____//_/ /_/ \__,_//_//_/ /_/ /_____//_/ /_/ \__,_/ \__, / \___/ + /____/ + PLUGIN SERVER CLI 0.2.0 + +>>> type help to see all commands... +ps> +ps> manage.reloadPluginInNewPath("simple-ethereum", "path/to/new/simple-ethereum-bbc-0.1-SNAPSHOT-plugin.jar") +``` + +#### 重启BBC实例 + +场景:当中继请求插件服务为某条链启动BBC实例之后,插件服务会从插件中读取相关Class,创建BBC实例,而更新了插件代码,经过插件的reload之后,需要重启BBC实例,才可以使用更新之后的代码提供服务。 + +命令:`manage.restartBBC(String product, String domain)` + +参数:参数`product`为插件中设置的区块链类型;参数`domain`是你想让插件服务重新创建的BBC实例; + +``` + ___ __ ______ __ _ ____ _ __ + / | ____ / /_ / ____// /_ ____ _ (_)____ / __ ) _____ (_)____/ /____ _ ___ + / /| | / __ \ / __// / / __ \ / __ `// // __ \ / __ |/ ___// // __ // __ `// _ \ + / ___ | / / / // /_ / /___ / / / // /_/ // // / / / / /_/ // / / // /_/ // /_/ // __/ +/_/ |_|/_/ /_/ \__/ \____//_/ /_/ \__,_//_//_/ /_/ /_____//_/ /_/ \__,_/ \__, / \___/ + /____/ + PLUGIN SERVER CLI 0.2.0 + +>>> type help to see all commands... +ps> +ps> manage.restartBBC("simple-ethereum", "domainA.eth.org") +success +``` + +#### 加载新插件 + +场景:让插件服务,加载一个新插件,插件的类型和ID必须没有被加载过。 + +命令:`manage.loadPlugin(String path)` + +参数:参数`path`指定新的插件文件路径,路径是相对插件服务进程的,相对路径和绝对路径都会被判断为相等; + +``` + ___ __ ______ __ _ ____ _ __ + / | ____ / /_ / ____// /_ ____ _ (_)____ / __ ) _____ (_)____/ /____ _ ___ + / /| | / __ \ / __// / / __ \ / __ `// // __ \ / __ |/ ___// // __ // __ `// _ \ + / ___ | / / / // /_ / /___ / / / // /_/ // // / / / / /_/ // / / // /_/ // /_/ // __/ +/_/ |_|/_/ /_/ \__/ \____//_/ /_/ \__,_//_//_/ /_/ /_____//_/ /_/ \__,_/ \__, / \___/ + /____/ + PLUGIN SERVER CLI 0.2.0 + +>>> type help to see all commands... +ps> +ps> manage.loadPlugin("path/to/simple-ethereum-bbc-0.1-SNAPSHOT-plugin.jar") +``` + +#### 启动新插件 + +场景:让插件服务,启动一个新加载的插件(重新加载的不需要),**只有启动之后的插件才可以用于创建BBC对象**。 + +命令:`manage.startPlugin(String path)` + +参数:参数`path`指定该插件文件路径,路径是相对插件服务进程的,相对路径和绝对路径都会被判断为相等; + +``` + ___ __ ______ __ _ ____ _ __ + / | ____ / /_ / ____// /_ ____ _ (_)____ / __ ) _____ (_)____/ /____ _ ___ + / /| | / __ \ / __// / / __ \ / __ `// // __ \ / __ |/ ___// // __ // __ `// _ \ + / ___ | / / / // /_ / /___ / / / // /_/ // // / / / / /_/ // / / // /_/ // /_/ // __/ +/_/ |_|/_/ /_/ \__/ \____//_/ /_/ \__,_//_//_/ /_/ /_____//_/ /_/ \__,_/ \__, / \___/ + /____/ + PLUGIN SERVER CLI 0.2.0 + +>>> type help to see all commands... +ps> +ps> manage.startPlugin("path/to/simple-ethereum-bbc-0.1-SNAPSHOT-plugin.jar") +``` + +#### 停止插件 + +场景:让插件服务,停止使用某个插件,只有启动之后的插件才可以停止。 + +命令:`manage.stopPlugin(String product)` + +参数:参数`product`为插件中设置的区块链类型; + +``` + ___ __ ______ __ _ ____ _ __ + / | ____ / /_ / ____// /_ ____ _ (_)____ / __ ) _____ (_)____/ /____ _ ___ + / /| | / __ \ / __// / / __ \ / __ `// // __ \ / __ |/ ___// // __ // __ `// _ \ + / ___ | / / / // /_ / /___ / / / // /_/ // // / / / / /_/ // / / // /_/ // /_/ // __/ +/_/ |_|/_/ /_/ \__/ \____//_/ /_/ \__,_//_//_/ /_/ /_____//_/ /_/ \__,_/ \__, / \___/ + /____/ + PLUGIN SERVER CLI 0.2.0 + +>>> type help to see all commands... +ps> +ps> manage.stopPlugin("simple-ethereum") +``` + +#### 启动已停止的插件 + +场景:让插件服务重新启动某个已经停止的插件,只有停止之后的插件才可以重新启动。 + +命令:`manage.startPluginFromStop(String product)` + +参数:参数`product`为插件中设置的区块链类型; + +``` + ___ __ ______ __ _ ____ _ __ + / | ____ / /_ / ____// /_ ____ _ (_)____ / __ ) _____ (_)____/ /____ _ ___ + / /| | / __ \ / __// / / __ \ / __ `// // __ \ / __ |/ ___// // __ // __ `// _ \ + / ___ | / / / // /_ / /___ / / / // /_/ // // / / / / /_/ // / / // /_/ // /_/ // __/ +/_/ |_|/_/ /_/ \__/ \____//_/ /_/ \__,_//_//_/ /_/ /_____//_/ /_/ \__,_/ \__, / \___/ + /____/ + PLUGIN SERVER CLI 0.2.0 + +>>> type help to see all commands... +ps> +ps> manage.startPluginFromStop("simple-ethereum") +``` + +#### 查询当前所有已启动插件的类型列表 + +场景:让插件服务返回当前所有已经启动的插件类型列表。 + +命令:`manage.allPlugins()` + +``` + ___ __ ______ __ _ ____ _ __ + / | ____ / /_ / ____// /_ ____ _ (_)____ / __ ) _____ (_)____/ /____ _ ___ + / /| | / __ \ / __// / / __ \ / __ `// // __ \ / __ |/ ___// // __ // __ `// _ \ + / ___ | / / / // /_ / /___ / / / // /_/ // // / / / / /_/ // / / // /_/ // /_/ // __/ +/_/ |_|/_/ /_/ \__/ \____//_/ /_/ \__,_//_//_/ /_/ /_____//_/ /_/ \__,_/ \__, / \___/ + /____/ + PLUGIN SERVER CLI 0.2.0 + +>>> type help to see all commands... +ps> manage.allPlugins() +simple-ethereum, testchain +``` + +#### 查询当前所有服务中的区块链域名列表 + +场景:让插件服务返回当前所有服务中的区块链域名列表 + +命令:` manage.allDomains()` + +``` + ___ __ ______ __ _ ____ _ __ + / | ____ / /_ / ____// /_ ____ _ (_)____ / __ ) _____ (_)____/ /____ _ ___ + / /| | / __ \ / __// / / __ \ / __ `// // __ \ / __ |/ ___// // __ // __ `// _ \ + / ___ | / / / // /_ / /___ / / / // /_/ // // / / / / /_/ // / / // /_/ // /_/ // __/ +/_/ |_|/_/ /_/ \__/ \____//_/ /_/ \__,_//_//_/ /_/ /_____//_/ /_/ \__,_/ \__, / \___/ + /____/ + PLUGIN SERVER CLI 0.2.0 + +>>> type help to see all commands... +ps> manage.allDomains() +chaina, chainb +``` + +#### 查询某些区块链是否有启动的插件 + +场景:查询插件服务某些指定的区块链类型是否有已经启动的插件。 + +命令:` manage.hasPlugins(String[] products...)` + +参数:`products`是区块链插件类型的数组; + +``` + ___ __ ______ __ _ ____ _ __ + / | ____ / /_ / ____// /_ ____ _ (_)____ / __ ) _____ (_)____/ /____ _ ___ + / /| | / __ \ / __// / / __ \ / __ `// // __ \ / __ |/ ___// // __ // __ `// _ \ + / ___ | / / / // /_ / /___ / / / // /_/ // // / / / / /_/ // / / // /_/ // /_/ // __/ +/_/ |_|/_/ /_/ \__/ \____//_/ /_/ \__,_//_//_/ /_/ /_____//_/ /_/ \__,_/ \__, / \___/ + /____/ + PLUGIN SERVER CLI 0.2.0 + +>>> type help to see all commands... +ps> manage.hasPlugins("simple-ethereum", "testchain", "notexist") +{ + "testchain":true, + "notexist":false, # false 代表不存在 + "simple-ethereum":true # true 代表存在 +} +``` + +#### 查询某些域名是否正在服务 + +场景:查询插件服务某些域名是否正在服务,即有正在运行的区块链BBC对象。 + +命令:` manage.hasDomains(String[] domains...)` + +参数:`domains`是区块链插件类型的数组; + +``` + ___ __ ______ __ _ ____ _ __ + / | ____ / /_ / ____// /_ ____ _ (_)____ / __ ) _____ (_)____/ /____ _ ___ + / /| | / __ \ / __// / / __ \ / __ `// // __ \ / __ |/ ___// // __ // __ `// _ \ + / ___ | / / / // /_ / /___ / / / // /_/ // // / / / / /_/ // / / // /_/ // /_/ // __/ +/_/ |_|/_/ /_/ \__/ \____//_/ /_/ \__,_//_//_/ /_/ /_____//_/ /_/ \__,_/ \__, / \___/ + /____/ + PLUGIN SERVER CLI 0.2.0 + +>>> type help to see all commands... +ps> manage.hasDomains("chaina", "chainb") +{ + "chainb":true, + "chaina":false +} +``` + diff --git a/ps-cli/desc_tar.xml b/ps-cli/desc_tar.xml new file mode 100644 index 0000000..b6b2330 --- /dev/null +++ b/ps-cli/desc_tar.xml @@ -0,0 +1,32 @@ + + ${version} + + tar.gz + + true + + + ${project.basedir}/target/boot/ + ${file.separator}lib + + ${artifactId}-${version}.jar + + + + ${project.basedir}/src/main/resources/scripts + ${file.separator}bin + + *.sh + + + + ${project.basedir}/ + ${file.separator} + + README.md + + + + \ No newline at end of file diff --git a/ps-cli/pom.xml b/ps-cli/pom.xml new file mode 100644 index 0000000..34ae3b2 --- /dev/null +++ b/ps-cli/pom.xml @@ -0,0 +1,108 @@ + + + 4.0.0 + + com.alipay.antchain.bridge + antchain-bridge-pluginserver + 0.2.0 + + + ps-cli + + + 8 + 8 + UTF-8 + + + + + com.alipay.antchain.bridge + ps-service + + + org.jline + jline + + + org.codehaus.groovy + groovy-all + pom + + + commons-cli + commons-cli + + + cn.hutool + hutool-all + + + io.grpc + grpc-netty-shaded + + + com.alibaba + fastjson + + + + + + + src/main/resources + + **/*.xml + **/VERSION + + + true + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + ./target/boot + + + + + repackage + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 3.1.0 + + + + plugin-server-cli + + desc_tar.xml + + + make-tar + package + + single + + + + + + + + \ No newline at end of file diff --git a/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/command/ArgsConstraint.java b/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/command/ArgsConstraint.java new file mode 100644 index 0000000..1567b28 --- /dev/null +++ b/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/command/ArgsConstraint.java @@ -0,0 +1,31 @@ +/* + * Copyright 2023 Ant Group + * + * Licensed 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 com.alipay.antchain.bridge.pluginserver.cli.command; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +public @interface ArgsConstraint { + + String name() default ""; + + String[] constraints() default ""; +} diff --git a/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/command/Command.java b/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/command/Command.java new file mode 100644 index 0000000..c2e7983 --- /dev/null +++ b/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/command/Command.java @@ -0,0 +1,112 @@ +/* + * Copyright 2023 Ant Group + * + * Licensed 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 com.alipay.antchain.bridge.pluginserver.cli.command; + +import java.util.ArrayList; +import java.util.List; + +public class Command { + + /** + * 命令名称 + */ + private String name; + + /** + * 命令参数描述 + */ + private List args = new ArrayList<>(); + + /** + * 使用命令名称构造命令 + * + * @param name + */ + public Command(String name) { + this.name = name; + } + + /** + * 添加参数 + * + * @param argName 参数名称 + * @param constraints 参数取值约束 + */ + public void addArgs(String argName, String type, List constraints) { + + Arg item = new Arg(); + item.name = argName; + item.type = type; + item.constraints = constraints; + + this.args.add(item); + } + + /** + * Getter method for property name.. + * + * @return property value of name. + */ + public String getName() { + return name; + } + + /** + * Getter method for property args.. + * + * @return property value of args. + */ + public List getArgs() { + return args; + } + + /** + * 参数描述类 + */ + public static class Arg { + private String name; + private String type; + private List constraints; + + /** + * Getter method for property name.. + * + * @return property value of name. + */ + public String getName() { + return name; + } + + /** + * Getter method for property constraints.. + * + * @return property value of constraints. + */ + public List getConstraints() { + return constraints; + } + + /** + * Getter method for property type.. + * + * @return property value of type. + */ + public String getType() { + return type; + } + } +} diff --git a/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/command/CommandHandler.java b/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/command/CommandHandler.java new file mode 100644 index 0000000..714d04e --- /dev/null +++ b/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/command/CommandHandler.java @@ -0,0 +1,31 @@ +/* + * Copyright 2023 Ant Group + * + * Licensed 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 com.alipay.antchain.bridge.pluginserver.cli.command; + +@FunctionalInterface +public interface CommandHandler { + + /** + * 执行命令 + * + * @param namespace 命令空间 + * @param command 命令 + * @param params 执行参数 + * @return + */ + Object execute(String namespace, String command, String... params); +} diff --git a/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/command/CommandNamespace.java b/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/command/CommandNamespace.java new file mode 100644 index 0000000..343a0e1 --- /dev/null +++ b/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/command/CommandNamespace.java @@ -0,0 +1,39 @@ +/* + * Copyright 2023 Ant Group + * + * Licensed 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 com.alipay.antchain.bridge.pluginserver.cli.command; + +import java.util.Map; + +/** + * 命令命名空间 + */ +public interface CommandNamespace { + + /** + * 命名空间名称 + * + * @return + */ + public String name(); + + /** + * 获取命令命名空间下所有命令 + * + * @return + */ + public Map getCommands(); +} diff --git a/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/command/CommandNamespaceImpl.java b/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/command/CommandNamespaceImpl.java new file mode 100644 index 0000000..9061215 --- /dev/null +++ b/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/command/CommandNamespaceImpl.java @@ -0,0 +1,55 @@ +/* + * Copyright 2023 Ant Group + * + * Licensed 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 com.alipay.antchain.bridge.pluginserver.cli.command; + +import java.util.HashMap; +import java.util.Map; + +public class CommandNamespaceImpl implements CommandNamespace { + /** + * 命令集合 + */ + private final Map commands = new HashMap<>(); + + /** + * 命名空间名称 + * + * @return + */ + @Override + public String name() { + return null; + } + + /** + * 往命名空间添加一个命令 + * + * @param cmd + */ + public void addCommand(Command cmd) { + commands.put(cmd.getName(), cmd); + } + + /** + * 获取命名空间下所有命令 + * + * @return + */ + public Map getCommands() { + return commands; + } +} diff --git a/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/command/ManagementCommandNamespace.java b/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/command/ManagementCommandNamespace.java new file mode 100644 index 0000000..37a4396 --- /dev/null +++ b/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/command/ManagementCommandNamespace.java @@ -0,0 +1,98 @@ +/* + * Copyright 2023 Ant Group + * + * Licensed 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 com.alipay.antchain.bridge.pluginserver.cli.command; + +import com.alipay.antchain.bridge.pluginserver.cli.shell.GroovyScriptCommandNamespace; + +public class ManagementCommandNamespace extends GroovyScriptCommandNamespace { + @Override + public String name() { + return "manage"; + } + + Object loadPlugins() { + return queryAPI("loadPlugins"); + } + + Object startPlugins() { + return queryAPI("startPlugins"); + } + + Object loadPlugin( + @ArgsConstraint(name = "path") String path + ) { + return queryAPI("loadPlugin", path); + } + + Object startPlugin( + @ArgsConstraint(name = "path") String path + ) { + return queryAPI("startPlugin", path); + } + + Object stopPlugin( + @ArgsConstraint(name = "product") String product + ) { + return queryAPI("stopPlugin", product); + } + + Object startPluginFromStop( + @ArgsConstraint(name = "product") String product + ) { + return queryAPI("startPluginFromStop", product); + } + + Object reloadPlugin( + @ArgsConstraint(name = "product") String product + ) { + return queryAPI("reloadPlugin", product); + } + + Object reloadPluginInNewPath( + @ArgsConstraint(name = "product") String product, + @ArgsConstraint(name = "path") String path + ) { + return queryAPI("reloadPluginInNewPath", product, path); + } + + Object hasPlugins( + @ArgsConstraint(name = "products...") String... products + ) { + return queryAPI("hasPlugins", products); + } + + Object allPlugins() { + return queryAPI("allPlugins"); + } + + Object hasDomains( + @ArgsConstraint(name = "domains...") String... domains + ) { + return queryAPI("hasDomains", domains); + } + + Object allDomains() { + return queryAPI("allDomains"); + } + + Object restartBBC( + @ArgsConstraint(name = "product") String product, + @ArgsConstraint(name = "domain") String domain + ) { + return queryAPI("restartBBC", product, domain); + } +} diff --git a/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/command/NamespaceManager.java b/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/command/NamespaceManager.java new file mode 100644 index 0000000..750b2d0 --- /dev/null +++ b/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/command/NamespaceManager.java @@ -0,0 +1,43 @@ +/* + * Copyright 2023 Ant Group + * + * Licensed 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 com.alipay.antchain.bridge.pluginserver.cli.command; + +import java.util.List; + +public interface NamespaceManager { + + /** + * 添加namespace + * + * @param commandNamespace + */ + void addNamespace(CommandNamespace commandNamespace); + + /** + * 获取所有namespace + * + * @return + */ + List getCommandNamespaces(); + + /** + * namespace快照 + * + * @return + */ + String dump(); +} diff --git a/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/command/NamespaceManagerImpl.java b/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/command/NamespaceManagerImpl.java new file mode 100644 index 0000000..083da20 --- /dev/null +++ b/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/command/NamespaceManagerImpl.java @@ -0,0 +1,68 @@ +/* + * Copyright 2023 Ant Group + * + * Licensed 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 com.alipay.antchain.bridge.pluginserver.cli.command; + +import java.util.ArrayList; +import java.util.List; + +public class NamespaceManagerImpl implements NamespaceManager { + + private final List commandNamespaces = new ArrayList<>(); + + public NamespaceManagerImpl() { + addNamespace(new ManagementCommandNamespace()); + } + + @Override + public void addNamespace(CommandNamespace commandNamespace) { + this.commandNamespaces.add(commandNamespace); + } + + @Override + public List getCommandNamespaces() { + return commandNamespaces; + } + + @Override + public String dump() { + StringBuilder builder = new StringBuilder(); + commandNamespaces.forEach( + commandNamespace -> { + builder.append("\n").append(commandNamespace.name()); + commandNamespace.getCommands().forEach( + (cmdName, cmd) -> { + builder.append("\n\t.").append(cmdName); + if (!cmd.getArgs().isEmpty()) { + builder.append("("); + cmd.getArgs().forEach( + arg -> { + builder.append(arg.getName()).append(","); + } + ); + builder.deleteCharAt(builder.length() - 1); + builder.append(")"); + } else { + builder.append("()"); + } + } + ); + } + ); + + return builder.append("\n\n").toString(); + } +} diff --git a/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/core/ManagementGrpcClient.java b/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/core/ManagementGrpcClient.java new file mode 100644 index 0000000..a6ac341 --- /dev/null +++ b/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/core/ManagementGrpcClient.java @@ -0,0 +1,133 @@ +/* + * Copyright 2023 Ant Group + * + * Licensed 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 com.alipay.antchain.bridge.pluginserver.cli.core; + +import java.io.IOException; +import java.net.Socket; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import cn.hutool.core.collection.CollUtil; +import com.alibaba.fastjson.JSON; +import com.alipay.antchain.bridge.pluginserver.managementservice.*; +import io.grpc.ManagedChannel; +import io.grpc.ManagedChannelBuilder; + +public class ManagementGrpcClient { + + private final ManagedChannel channel; + private ManagementServiceGrpc.ManagementServiceBlockingStub blockingStub; + + private final String host; + + private final int port; + + public ManagementGrpcClient(int port) { + this("127.0.0.1", port); + } + + public ManagementGrpcClient(String host, int port) { + this.port = port; + this.host = host; + this.channel = ManagedChannelBuilder.forAddress(this.host, this.port) + .usePlaintext() + .build(); + this.blockingStub = ManagementServiceGrpc.newBlockingStub(channel); + } + + public boolean checkServerStatus() { + try { + Socket socket = new Socket(host, port); + socket.close(); + return true; + } catch (IOException e) { + return false; + } + } + + public void shutdown() throws InterruptedException { + channel.shutdown().awaitTermination(5, TimeUnit.SECONDS); + } + + public String managePlugin(PluginManageRequest.Type type, String product, String path) { + ManageResponse response = this.blockingStub.managePlugin( + PluginManageRequest.newBuilder() + .setType(type) + .setPath(path) + .setProduct(product) + .build() + ); + return response.getCode() == 0 ? "success" : "failed with msg: " + response.getErrorMsg(); + } + + public String hasPlugins(List productList) { + ManageResponse response = this.blockingStub.hasPlugins( + HasPluginsRequest.newBuilder() + .addAllProducts(productList) + .build() + ); + if (response.getCode() != 0) { + return "failed with msg: " + response.getErrorMsg(); + } + + return JSON.toJSONString(response.getHasPluginsResp().getResultsMap()); + } + + public String getAllPlugins() { + ManageResponse response = this.blockingStub.allPlugins(AllPluginsRequest.newBuilder().build()); + if (response.getCode() != 0) { + return "failed with msg: " + response.getErrorMsg(); + } + + return CollUtil.join(response.getAllPluginsResp().getProductsList(), ", "); + } + + public String hasDomains(List domains) { + ManageResponse response = this.blockingStub.hasDomains( + HasDomainsRequest.newBuilder() + .addAllDomains(domains) + .build() + ); + if (response.getCode() != 0) { + return "failed with msg: " + response.getErrorMsg(); + } + + return JSON.toJSONString(response.getHasDomainsResp().getResultsMap()); + } + + public String getAllDomains() { + ManageResponse response = this.blockingStub.allDomains(AllDomainsRequest.newBuilder().build()); + if (response.getCode() != 0) { + return "failed with msg: " + response.getErrorMsg(); + } + + return CollUtil.join(response.getAllDomainsResp().getDomainsList(), ", "); + } + + public String restartBBC(String product, String domain) { + ManageResponse response = this.blockingStub.restartBBC( + RestartBBCRequest.newBuilder() + .setProduct(product) + .setDomain(domain) + .build() + ); + if (response.getCode() != 0) { + return "failed with msg: " + response.getErrorMsg(); + } + return "success"; + } +} diff --git a/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/shell/GlLineReader.java b/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/shell/GlLineReader.java new file mode 100644 index 0000000..b718cd3 --- /dev/null +++ b/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/shell/GlLineReader.java @@ -0,0 +1,354 @@ +/* + * Copyright 2023 Ant Group + * + * Licensed 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 com.alipay.antchain.bridge.pluginserver.cli.shell; + +import java.io.IOException; +import java.util.*; +import java.util.Map.Entry; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import org.jline.reader.Binding; +import org.jline.reader.Candidate; +import org.jline.reader.ParsedLine; +import org.jline.reader.Parser.ParseContext; +import org.jline.reader.Reference; +import org.jline.reader.impl.LineReaderImpl; +import org.jline.reader.impl.ReaderUtils; +import org.jline.terminal.Terminal; +import org.jline.utils.AttributedString; +import org.jline.utils.Levenshtein; +import org.jline.utils.Log; + +public class GlLineReader extends LineReaderImpl { + + public GlLineReader(Terminal terminal) throws IOException { + super(terminal); + } + + public GlLineReader(Terminal terminal, String appName) throws IOException { + super(terminal, appName); + } + + public GlLineReader(Terminal terminal, String appName, Map variables) { + super(terminal, appName, variables); + } + + @Override + protected boolean insertTab() { + return isSet(Option.INSERT_TAB) && false; + } + + @Override + protected boolean doComplete(CompletionType lst, boolean useMenu, boolean prefix) { + // Try to expand history first + // If there is actually an expansion, bail out now + try { + if (expandHistory()) { + return true; + } + } catch (Exception e) { + Log.info("Error while expanding history", e); + return false; + } + + // Parse the command line + ParsedLine line; + try { + line = parser.parse(buf.toString(), buf.cursor(), ParseContext.COMPLETE); + } catch (Exception e) { + Log.info("Error while parsing line", e); + return false; + } + + // Find completion candidates + List candidates = new ArrayList<>(); + try { + if (completer != null) { + completer.complete(this, line, candidates); + } + } catch (Exception e) { + Log.info("Error while finding completion candidates", e); + return false; + } + + if (lst == CompletionType.ExpandComplete || lst == CompletionType.Expand) { + String w = expander.expandVar(line.word()); + if (!line.word().equals(w)) { + if (prefix) { + buf.backspace(line.wordCursor()); + } else { + buf.move(line.word().length() - line.wordCursor()); + buf.backspace(line.word().length()); + } + buf.write(w); + return true; + } + if (lst == CompletionType.Expand) { + return false; + } else { + lst = CompletionType.Complete; + } + } + + boolean caseInsensitive = isSet(Option.CASE_INSENSITIVE); + int errors = getInt(ERRORS, DEFAULT_ERRORS); + + // Build a list of sorted candidates + NavigableMap> sortedCandidates = + new TreeMap<>(caseInsensitive ? String.CASE_INSENSITIVE_ORDER : null); + for (Candidate cand : candidates) { + sortedCandidates + .computeIfAbsent(AttributedString.fromAnsi(cand.value()).toString(), s -> new ArrayList<>()) + .add(cand); + } + + // Find matchers + // TODO: glob completion + List>, + Map>>> matchers; + Predicate exact; + if (prefix) { + String wp = line.word().substring(0, line.wordCursor()); + matchers = Arrays.asList( + simpleMatcher(s -> s.startsWith(wp)), + simpleMatcher(s -> s.contains(wp)), + typoMatcher(wp, errors) + ); + exact = s -> s.equals(wp); + } else if (isSet(Option.COMPLETE_IN_WORD)) { + String wd = line.word(); + String wp = wd.substring(0, line.wordCursor()); + String ws = wd.substring(line.wordCursor()); + Pattern p1 = Pattern.compile(Pattern.quote(wp) + ".*" + Pattern.quote(ws) + ".*"); + Pattern p2 = Pattern.compile(".*" + Pattern.quote(wp) + ".*" + Pattern.quote(ws) + ".*"); + matchers = Arrays.asList( + simpleMatcher(s -> p1.matcher(s).matches()), + simpleMatcher(s -> p2.matcher(s).matches()), + typoMatcher(wd, errors) + ); + exact = s -> s.equals(wd); + } else { + String wd = line.word(); + matchers = Arrays.asList( + simpleMatcher(s -> s.startsWith(wd)), + simpleMatcher(s -> s.contains(wd)), + typoMatcher(wd, errors) + ); + exact = s -> s.equals(wd); + } + // Find matching candidates + Map> matching = Collections.emptyMap(); + for (Function>, + Map>> matcher : matchers) { + matching = matcher.apply(sortedCandidates); + if (!matching.isEmpty()) { + break; + } + } + + // If we have no matches, bail out + if (matching.isEmpty()) { + return false; + } + + // If we only need to display the list, do it now + if (lst == CompletionType.List) { + List possible = matching.entrySet().stream() + .flatMap(e -> e.getValue().stream()) + .collect(Collectors.toList()); + doList(possible, line.word(), false); + return !possible.isEmpty(); + } + + // Check if there's a single possible match + Candidate completion = null; + // If there's a single possible completion + if (matching.size() == 1) { + completion = matching.values().stream().flatMap(Collection::stream) + .findFirst().orElse(null); + } + // Or if RECOGNIZE_EXACT is set, try to find an exact match + else if (isSet(Option.RECOGNIZE_EXACT)) { + completion = matching.values().stream().flatMap(Collection::stream) + .filter(Candidate::complete) + .filter(c -> exact.test(c.value())) + .findFirst().orElse(null); + } + // Complete and exit + if (completion != null && !completion.value().isEmpty()) { + if (prefix) { + buf.backspace(line.wordCursor()); + } else { + buf.move(line.line().length() - line.cursor()); + buf.backspace(line.line().length()); + } + buf.write(completion.value()); + if (completion.suffix() != null) { + redisplay(); + + Binding op = readBinding(getKeys()); + if (op != null) { + String chars = getString(REMOVE_SUFFIX_CHARS, DEFAULT_REMOVE_SUFFIX_CHARS); + String ref = op instanceof Reference ? ((Reference)op).name() : null; + if (SELF_INSERT.equals(ref) && chars.indexOf(getLastBinding().charAt(0)) >= 0 + || ACCEPT_LINE.equals(ref)) { + buf.backspace(completion.suffix().length()); + if (getLastBinding().charAt(0) != ' ') { + buf.write(' '); + } + } + pushBackBinding(true); + } + } + + List possible = matching.entrySet().stream() + .flatMap(e -> e.getValue().stream()) + .collect(Collectors.toList()); + + if (isSet(Option.AUTO_LIST)) { + if (!doList(possible, line.word(), true)) { + return true; + } + } + + return true; + } + + List possible = matching.entrySet().stream() + .flatMap(e -> e.getValue().stream()) + .collect(Collectors.toList()); + + if (useMenu) { + buf.move(line.word().length() - line.wordCursor()); + buf.backspace(line.word().length()); + doMenu(possible, line.word()); + return true; + } + + // Find current word and move to end + String current; + if (prefix) { + current = line.word().substring(0, line.wordCursor()); + } else { + current = line.word(); + buf.move(current.length() - line.wordCursor()); + } + // Now, we need to find the unambiguous completion + // TODO: need to find common suffix + String commonPrefix = null; + for (String key : matching.keySet()) { + commonPrefix = commonPrefix == null ? key : getCommonStart(commonPrefix, key, caseInsensitive); + } + boolean hasUnambiguous = commonPrefix.startsWith(current) && !commonPrefix.equals(current); + + if (hasUnambiguous) { + buf.backspace(current.length()); + buf.write(commonPrefix); + current = commonPrefix; + if ((!isSet(Option.AUTO_LIST) && isSet(Option.AUTO_MENU)) + || (isSet(Option.AUTO_LIST) && isSet(Option.LIST_AMBIGUOUS))) { + if (!nextBindingIsComplete()) { + return true; + } + } + } + if (isSet(Option.AUTO_LIST)) { + if (!doList(possible, current, true)) { + return true; + } + } + if (isSet(Option.AUTO_MENU)) { + buf.backspace(current.length()); + doMenu(possible, line.word()); + } + return true; + } + + int getInt(String name, int def) { + return ReaderUtils.getInt(this, name, def); + } + + private String getCommonStart(String str1, String str2, boolean caseInsensitive) { + int[] s1 = str1.codePoints().toArray(); + int[] s2 = str2.codePoints().toArray(); + int len = 0; + while (len < Math.min(s1.length, s2.length)) { + int ch1 = s1[len]; + int ch2 = s2[len]; + if (ch1 != ch2 && caseInsensitive) { + ch1 = Character.toUpperCase(ch1); + ch2 = Character.toUpperCase(ch2); + if (ch1 != ch2) { + ch1 = Character.toLowerCase(ch1); + ch2 = Character.toLowerCase(ch2); + } + } + if (ch1 != ch2) { + break; + } + len++; + } + return new String(s1, 0, len); + } + + private void pushBackBinding(boolean skip) { + String s = getLastBinding(); + if (s != null) { + bindingReader.runMacro(s); + skipRedisplay = skip; + } + } + + String getString(String name, String def) { + return ReaderUtils.getString(this, name, def); + } + + private Function>, + Map>> simpleMatcher(Predicate pred) { + return m -> m.entrySet().stream() + .filter(e -> pred.test(e.getKey())) + .collect(Collectors.toMap(Entry::getKey, Entry::getValue)); + } + + private Function>, + Map>> typoMatcher(String word, int errors) { + return m -> { + Map> map = m.entrySet().stream() + .filter(e -> distance(word, e.getKey()) < errors) + .collect(Collectors.toMap(Entry::getKey, Entry::getValue)); + if (map.size() > 1) { + map.computeIfAbsent(word, w -> new ArrayList<>()) + .add(new Candidate(word, word, "original", null, null, null, false)); + } + return map; + }; + } + + private int distance(String word, String cand) { + if (word.length() < cand.length()) { + int d1 = Levenshtein.distance(word, cand.substring(0, Math.min(cand.length(), word.length()))); + int d2 = Levenshtein.distance(word, cand); + return Math.min(d1, d2); + } else { + return Levenshtein.distance(word, cand); + } + } + +} diff --git a/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/shell/GroovyScriptCommandNamespace.java b/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/shell/GroovyScriptCommandNamespace.java new file mode 100644 index 0000000..6d8a0ff --- /dev/null +++ b/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/shell/GroovyScriptCommandNamespace.java @@ -0,0 +1,205 @@ +/* + * Copyright 2023 Ant Group + * + * Licensed 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 com.alipay.antchain.bridge.pluginserver.cli.shell; + +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.List; +import java.util.stream.Stream; + +import cn.hutool.core.collection.ListUtil; +import com.alipay.antchain.bridge.pluginserver.cli.command.ArgsConstraint; +import com.alipay.antchain.bridge.pluginserver.cli.command.Command; +import com.alipay.antchain.bridge.pluginserver.cli.command.CommandNamespaceImpl; +import com.alipay.antchain.bridge.pluginserver.managementservice.PluginManageRequest; +import com.google.common.collect.Lists; + +public abstract class GroovyScriptCommandNamespace extends CommandNamespaceImpl { + + private static final String COMMAND_NAMESPACE_NAME = "name"; + + /** + * 命名空间名称由子类实现 + * + * @return + */ + @Override + public abstract String name(); + + public GroovyScriptCommandNamespace() { + super(); + loadCommand(); + } + + /** + * 初始化:加载command,默认将子类所有方法解析为命令 + */ + public void loadCommand() { + + Method[] methods = this.getClass().getDeclaredMethods(); + + Stream.of(methods).forEach(method -> { + + if (COMMAND_NAMESPACE_NAME.equals(method.getName())) { + return; + } + + Command cmd = new Command(method.getName()); + + Parameter[] params = method.getParameters(); + + for (Parameter param : params) { + + String argName = param.getName(); + List constraints = Lists.newArrayList(); + + ArgsConstraint argsConstraint = param.getAnnotation(ArgsConstraint.class); + + if (null != argsConstraint) { + if (null != argsConstraint.name() && !"".equals(argsConstraint.name().trim())) { + argName = argsConstraint.name().trim(); + } + if (null != argsConstraint.constraints()) { + Stream.of(argsConstraint.constraints()).filter( + constraint -> null != constraint && !"".equals(constraint.trim())).forEach( + constraint -> constraints.add(constraint)); + } + } + + cmd.addArgs(argName, param.getType().getSimpleName(), constraints); + } + addCommand(cmd); + }); + } + + protected String queryAPI(String command, Object... args) { + + if (args != null) { + String[] strArgs = new String[args.length]; + for (int i = 0; i < args.length; ++i) { + strArgs[i] = args[i].toString(); + } + + return queryAPI(command, strArgs); + } else { + + return queryAPI(command); + } + } + + /** + * 查询api,供子类命令执行使用 + * + * @param command + * @param args + * @return + */ + protected String queryAPI(String command, String... args) { + + switch (command) { + case "loadPlugins": + return Shell.RUNTIME.getGrpcClient().managePlugin( + PluginManageRequest.Type.LOAD_PLUGINS, + "", "" + ); + case "startPlugins": + return Shell.RUNTIME.getGrpcClient().managePlugin( + PluginManageRequest.Type.START_PLUGINS, + "", "" + ); + case "reloadPlugin": + if (args.length != 1) { + return "wrong length of arguments"; + } + return Shell.RUNTIME.getGrpcClient().managePlugin( + PluginManageRequest.Type.RELOAD_PLUGIN, + args[0], "" + ); + case "reloadPluginInNewPath": + if (args.length != 2) { + return "wrong length of arguments"; + } + return Shell.RUNTIME.getGrpcClient().managePlugin( + PluginManageRequest.Type.RELOAD_PLUGIN_IN_NEW_PATH, + args[0], args[1] + ); + case "startPlugin": + if (args.length != 1) { + return "wrong length of arguments"; + } + return Shell.RUNTIME.getGrpcClient().managePlugin( + PluginManageRequest.Type.START_PLUGIN, + "", args[0] + ); + case "stopPlugin": + if (args.length != 1) { + return "wrong length of arguments"; + } + return Shell.RUNTIME.getGrpcClient().managePlugin( + PluginManageRequest.Type.STOP_PLUGIN, + args[0], "" + ); + case "loadPlugin": + if (args.length != 1) { + return "wrong length of arguments"; + } + return Shell.RUNTIME.getGrpcClient().managePlugin( + PluginManageRequest.Type.LOAD_PLUGIN, + "", args[0] + ); + case "startPluginFromStop": + if (args.length != 1) { + return "wrong length of arguments"; + } + return Shell.RUNTIME.getGrpcClient().managePlugin( + PluginManageRequest.Type.START_PLUGIN_FROM_STOP, + args[0], "" + ); + case "hasPlugins": + if (args.length == 0) { + return "zero arguments"; + } + return Shell.RUNTIME.getGrpcClient().hasPlugins(ListUtil.toList(args)); + case "allPlugins": + if (args.length != 0) { + return "wrong length of arguments"; + } + return Shell.RUNTIME.getGrpcClient().getAllPlugins(); + case "hasDomains": + if (args.length == 0) { + return "zero arguments"; + } + return Shell.RUNTIME.getGrpcClient().hasDomains(ListUtil.toList(args)); + case "allDomains": + if (args.length != 0) { + return "wrong length of arguments"; + } + return Shell.RUNTIME.getGrpcClient().getAllDomains(); + case "restartBBC": + if (args.length != 2) { + return "wrong length of arguments"; + } + return Shell.RUNTIME.getGrpcClient().restartBBC(args[0], args[1]); + default: + return "wrong command " + command; + } + } + + protected void print(String result) { + Shell.RUNTIME.getPrinter().println(result); + } +} diff --git a/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/shell/GroovyShellProvider.java b/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/shell/GroovyShellProvider.java new file mode 100644 index 0000000..ee62386 --- /dev/null +++ b/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/shell/GroovyShellProvider.java @@ -0,0 +1,68 @@ +/* + * Copyright 2023 Ant Group + * + * Licensed 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 com.alipay.antchain.bridge.pluginserver.cli.shell; + +import java.beans.Introspector; + +import com.alipay.antchain.bridge.pluginserver.cli.command.NamespaceManager; +import groovy.lang.GroovyShell; +import groovy.lang.Script; +import org.codehaus.groovy.reflection.ClassInfo; +import org.codehaus.groovy.runtime.InvokerHelper; + +public class GroovyShellProvider extends GroovyShell implements ShellProvider { + + private NamespaceManager namespaceManager; + + public GroovyShellProvider(NamespaceManager namespaceManager) { + this.namespaceManager = namespaceManager; + + // init GroovyShell + this.namespaceManager.getCommandNamespaces().forEach(namespace -> { + + // only load GroovyScriptCommandNamespace + if (namespace instanceof GroovyScriptCommandNamespace) { + this.setVariable(namespace.name(), namespace); + } + }); + } + + private int cleanCount = 0; + + private static int CLEAN_PERIOD = 20; + + @Override + public String execute(String cmd) { + Script shell = this.parse(cmd); + Object scriptObject = InvokerHelper.createScript(shell.getClass(), this.getContext()).run(); + + // 周期清除缓存,防止OOM + if((++cleanCount) % CLEAN_PERIOD == 0) { + getClassLoader().clearCache(); + ClassInfo.clearModifiedExpandos(); + Introspector.flushCaches(); + } + + // execute by groovy script + return scriptObject.toString(); + } + + @Override + public void shutdown() { + // nothing + } +} diff --git a/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/shell/JsonUtil.java b/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/shell/JsonUtil.java new file mode 100644 index 0000000..793a37d --- /dev/null +++ b/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/shell/JsonUtil.java @@ -0,0 +1,65 @@ + +/* + * Copyright 2023 Ant Group + * + * Licensed 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 com.alipay.antchain.bridge.pluginserver.cli.shell; + +public class JsonUtil { + /** + * 得到格式化json数据 退格用\t 换行用\r + */ + public static String format(String jsonStr) { + int level = 0; + StringBuffer jsonForMatStr = new StringBuffer(); + for (int i = 0; i < jsonStr.length(); i++) { + char c = jsonStr.charAt(i); + if (level > 0 && '\n' == jsonForMatStr.charAt(jsonForMatStr.length() - 1)) { + jsonForMatStr.append(getLevelStr(level)); + } + switch (c) { + case '{': + case '[': + jsonForMatStr.append(c + "\n"); + level++; + break; + case ',': + jsonForMatStr.append(c + "\n"); + break; + case '}': + case ']': + jsonForMatStr.append("\n"); + level--; + jsonForMatStr.append(getLevelStr(level)); + jsonForMatStr.append(c); + break; + default: + jsonForMatStr.append(c); + break; + } + } + + return jsonForMatStr.toString(); + + } + + private static String getLevelStr(int level) { + StringBuffer levelStr = new StringBuffer(); + for (int levelI = 0; levelI < level; levelI++) { + levelStr.append("\t"); + } + return levelStr.toString(); + } +} diff --git a/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/shell/Launcher.java b/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/shell/Launcher.java new file mode 100644 index 0000000..a57a835 --- /dev/null +++ b/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/shell/Launcher.java @@ -0,0 +1,149 @@ +/* + * Copyright 2023 Ant Group + * + * Licensed 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 com.alipay.antchain.bridge.pluginserver.cli.shell; + +import java.io.*; +import java.nio.charset.Charset; + +import cn.hutool.core.io.resource.ResourceUtil; +import com.alipay.antchain.bridge.pluginserver.cli.command.NamespaceManager; +import com.alipay.antchain.bridge.pluginserver.cli.command.NamespaceManagerImpl; +import com.alipay.antchain.bridge.pluginserver.cli.core.ManagementGrpcClient; +import org.apache.commons.cli.*; + +public class Launcher { + private static final String OP_HELP = "h"; + private static final String OP_VERSION = "v"; + private static final String OP_PORT = "p"; + private static final String OP_CMD = "c"; + private static final String OP_FILE = "f"; + + private static final String OP_HOST = "H"; + + private static final Options options; + + static { + options = new Options(); + options.addOption(OP_HELP, "help", false, "print help info"); + options.addOption(OP_VERSION, "version", false, "print version info"); + options.addOption(OP_PORT, "port", true, "management server port"); + options.addOption(OP_CMD, "command", true, "execute the command"); + options.addOption(OP_FILE, "file", true, "execute multiple commands in the file"); + options.addOption(OP_HOST, "host", true, "set the host"); + } + + public static void main(String[] args) throws ParseException { + + CommandLineParser parser = new DefaultParser(); + CommandLine cmd = parser.parse(options, args); + + if (cmd.hasOption(OP_HELP)) { + HelpFormatter helpFormatter = new HelpFormatter(); + helpFormatter.printHelp("plugin server CLI", options); + return; + } + + if (cmd.hasOption(OP_VERSION)) { + System.out.printf("cli version : %s\n", getVersion()); + return; + } + + int port = 9091; + if (cmd.hasOption(OP_PORT)) { + port = Integer.parseInt(cmd.getOptionValue(OP_PORT)); + } + + // new namespace + NamespaceManager namespaceManager = new NamespaceManagerImpl(); + + // new shellProvider + ShellProvider shellProvider = new GroovyShellProvider(namespaceManager); + + // new promptCompleter + PromptCompleter promptCompleter = new PromptCompleter(); + promptCompleter.addNamespace(namespaceManager); + + // new grpcClient + String host = "localhost"; + if (cmd.hasOption(OP_HOST)) { + host = cmd.getOptionValue(OP_HOST); + } + ManagementGrpcClient grpcClient = new ManagementGrpcClient(host, port); + + if (!grpcClient.checkServerStatus()) { + System.out.printf("start failed, can't connect to local server port: %d\n", port); + return; + } + + // new shell + Shell shell = new Shell(shellProvider, promptCompleter, grpcClient, namespaceManager); + + if (cmd.hasOption(OP_CMD)) { + String command = cmd.getOptionValue(OP_CMD); + + try { + String result = shell.execute(command); + System.out.println(result); + } catch (Exception e) { + System.out.printf("illegal command [ %s ], execute failed\n", command); + } + return; + } + + if (cmd.hasOption(OP_FILE)) { + String filePath = cmd.getOptionValue(OP_FILE); + + String command = ""; + try { + BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(new File(filePath)))); + + command = reader.readLine(); + StringBuilder resultBuilder = new StringBuilder(); + while (null != command) { + try { + String result = shell.execute(command); + resultBuilder.append(result).append("\n"); + } catch (Exception e) { + resultBuilder.append("error\n"); + } + command = reader.readLine(); + } + + System.out.println(resultBuilder); + + } catch (FileNotFoundException e) { + System.out.printf("error: file %s not found\n", filePath); + } catch (IOException e) { + System.out.println("error: io exception"); + e.printStackTrace(); + } catch (Exception e) { + System.out.printf("illegal command [ %s ], execute failed\n", command); + e.printStackTrace(); + } + + return; + } + + shell.start(); + + Runtime.getRuntime().addShutdownHook(new Thread(shell::stop)); + } + + public static String getVersion() { + return ResourceUtil.readStr("VERSION", Charset.defaultCharset()); + } +} diff --git a/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/shell/PromptCompleter.java b/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/shell/PromptCompleter.java new file mode 100644 index 0000000..f85d476 --- /dev/null +++ b/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/shell/PromptCompleter.java @@ -0,0 +1,200 @@ +/* + * Copyright 2023 Ant Group + * + * Licensed 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 com.alipay.antchain.bridge.pluginserver.cli.shell; + +import java.util.ArrayList; +import java.util.List; + +import com.alipay.antchain.bridge.pluginserver.cli.command.CommandNamespace; +import com.alipay.antchain.bridge.pluginserver.cli.command.NamespaceManager; +import org.jline.reader.Candidate; +import org.jline.reader.Completer; +import org.jline.reader.LineReader; +import org.jline.reader.ParsedLine; + +public class PromptCompleter implements Completer { + + private List namespaces = new ArrayList<>(); + + private List reservedWords = new ArrayList<>(); + + /** + * 添加namespace + * + * @param namespaceManager + */ + public void addNamespace(NamespaceManager namespaceManager) { + + namespaces.addAll(namespaceManager.getCommandNamespaces()); + } + + /** + * 添加保留字 + * + * @param reservedWord + */ + public void addReservedWord(String reservedWord) { + this.reservedWords.add(reservedWord); + } + + @Override + public void complete(LineReader lineReader, ParsedLine commandLine, List candidates) { + assert commandLine != null; + assert candidates != null; + + String buffer = commandLine.line().substring(0, commandLine.cursor()); + + //如果未输入.符号,则补全保留字与命令 + if (!buffer.contains(".")) { + + // 补全保留字 + reservedWords.forEach(reservedWord -> { + if (!buffer.isEmpty() && !reservedWord.startsWith(buffer)) { + return; + } + + candidates.add(new Candidate(reservedWord, reservedWord, null, null, null, null, true)); + }); + + // 补全命令 + namespaces.forEach(namespace -> { + + if (!buffer.isEmpty() && !namespace.name().startsWith(buffer)) { + return; + } + + StringBuilder sb = new StringBuilder(namespace.name()); + namespace.getCommands().forEach((cmdName, cmd) -> { + + sb.append("\n\t.").append(cmdName); + if (!cmd.getArgs().isEmpty()) { + sb.append("("); + cmd.getArgs().forEach(arg -> { + sb.append("String ").append(arg.getName()).append(","); + }); + sb.deleteCharAt(sb.length() - 1); + sb.append(")"); + + } else { + sb.append("()"); + } + }); + + candidates.add(new Candidate(namespace.name() + ".", namespace.name(), null, null, null, null, true)); + }); + } else if (buffer.contains("(")) {// 已输入完整的命令(判断是否已输入"("符号),则补全详细的参数字段 + + String[] buf = buffer.split("\\."); + + namespaces.forEach(namespace -> { + + if (!namespace.name().equals(buf[0])) { + return; + } + namespace.getCommands().forEach((cmdName, cmd) -> { + + String command = buf[1].split("\\(")[0]; + + if (cmdName.equals(command)) { + + StringBuilder sb = new StringBuilder(cmdName); + if (!cmd.getArgs().isEmpty()) { + sb.append("("); + cmd.getArgs().forEach(arg -> { + sb.append("\n ").append(arg.getType()).append(" ").append(arg.getName()).append(" "); + + if (!arg.getConstraints().isEmpty()) { + sb.append(" //"); + arg.getConstraints().forEach(contraint -> { + sb.append(contraint).append(","); + }); + sb.deleteCharAt(sb.length() - 1); + } + }); + sb.append("\n)"); + + candidates.add( + new Candidate(buffer, sb.toString(), null, null, null, + null, true)); + } else { + sb.append("()"); + candidates.add( + new Candidate(namespace.name() + "." + cmdName + "()", sb.toString(), null, null, null, + null, true)); + } + } + }); + }); + } else { //已输入.符号,则补全匹配命令 + String[] buf = buffer.split("\\."); + + namespaces.forEach(namespace -> { + + if (!namespace.name().equals(buf[0])) { + return; + } + + long matchCount = namespace.getCommands().keySet().stream().filter( + cmdName -> cmdName.startsWith(buf.length <= 1 ? "" : buf[1])).count(); + + namespace.getCommands().forEach((cmdName, cmd) -> { + + if (cmdName.startsWith(buf.length <= 1 ? "" : buf[1])) { + + StringBuilder sb = new StringBuilder(cmdName); + if (cmd.getArgs().isEmpty()) { + sb.append("()"); + candidates.add( + new Candidate(namespace.name() + "." + cmdName + "()", sb.toString(), null, null, null, + null, true)); + + } else if (matchCount == 1) { + sb.append("("); + cmd.getArgs().forEach(arg -> { + sb.append("\n ").append(arg.getType()).append(" ").append(arg.getName()).append(" "); + + if (!arg.getConstraints().isEmpty()) { + sb.append(" //"); + arg.getConstraints().forEach(contraint -> { + sb.append(contraint).append(","); + }); + sb.deleteCharAt(sb.length() - 1); + } + }); + sb.append("\n)"); + + candidates.add( + new Candidate(namespace.name() + "." + cmdName + "(", sb.toString(), null, null, null, + null, true)); + } else { + sb.append("("); + cmd.getArgs().forEach(arg -> { + sb.append(arg.getType() + " " + arg.getName()).append(","); + }); + sb.deleteCharAt(sb.length() - 1); + sb.append(")"); + + candidates.add( + new Candidate(namespace.name() + "." + cmdName + "(", sb.toString(), null, null, null, + null, true)); + } + } + }); + }); + } + } +} diff --git a/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/shell/ReservedWord.java b/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/shell/ReservedWord.java new file mode 100644 index 0000000..30affc4 --- /dev/null +++ b/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/shell/ReservedWord.java @@ -0,0 +1,23 @@ +/* + * Copyright 2023 Ant Group + * + * Licensed 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 com.alipay.antchain.bridge.pluginserver.cli.shell; + +@FunctionalInterface +public interface ReservedWord { + + void execute(); +} diff --git a/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/shell/Shell.java b/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/shell/Shell.java new file mode 100644 index 0000000..1642789 --- /dev/null +++ b/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/shell/Shell.java @@ -0,0 +1,220 @@ +/* + * Copyright 2023 Ant Group + * + * Licensed 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 com.alipay.antchain.bridge.pluginserver.cli.shell; + +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.ReentrantLock; + +import com.alipay.antchain.bridge.pluginserver.cli.command.NamespaceManager; +import com.alipay.antchain.bridge.pluginserver.cli.core.ManagementGrpcClient; +import org.jline.reader.LineReader; +import org.jline.reader.LineReader.Option; +import org.jline.reader.impl.history.DefaultHistory; +import org.jline.terminal.Terminal; +import org.jline.terminal.TerminalBuilder; + +public class Shell { + + private static final String PROMPT = "\033[0;37mps> \033[0m"; + + private final NamespaceManager namespaceManager; + + private Terminal terminal; + + private GlLineReader reader; + + private final Map reservedWord = new HashMap<>(); + + private AtomicBoolean loopRunning = new AtomicBoolean(false); + + private final ReentrantLock shellLock = new ReentrantLock(); + + private final PromptCompleter completer; + + private final ShellProvider shellProvider; + + public final static Runtime RUNTIME = new Runtime(); + + public Shell(ShellProvider shellProvider, PromptCompleter completer, ManagementGrpcClient grpcClient, + NamespaceManager namespaceManager) { + + // 不可扩展参数初始化 + init(); + + // 扩展初始化 + this.shellProvider = shellProvider; + this.namespaceManager = namespaceManager; + + this.completer = completer; + this.reservedWord.keySet().forEach(reservedWord -> this.completer.addReservedWord(reservedWord)); + + reader.setCompleter(completer); + + // 运行时设置 + RUNTIME.setGrpcClient(grpcClient); + } + + void init() { + // init term + try { + terminal = TerminalBuilder.builder() + .system(true) + .build(); + } catch (IOException e) { + throw new RuntimeException("can't open system stream"); + } + + // set printer + RUNTIME.setPrinter(terminal.writer()); + + // init linereader + reader = new GlLineReader(terminal, "mychain-gl", new HashMap<>()); + + reader.setVariable(LineReader.HISTORY_FILE, Paths.get("./clihistory.tmp")); + reader.setHistory(new DefaultHistory(reader)); + + reader.unsetOpt(Option.MENU_COMPLETE); + reader.setOpt(Option.AUTO_LIST); + reader.unsetOpt(Option.AUTO_MENU); + + + // init shell commands + initReservedWord(); + } + + public void start() { + + try { + if (shellLock.tryLock()) { + + if (loopRunning.get()) { + return; + } + + loopRunning.set(true); + + welcome(); + + new Thread(() -> { + // start loop + while (loopRunning.get()) { + String cmd = reader.readLine(PROMPT); + + if (null == cmd || cmd.isEmpty()) { + continue; + } + + try { + if (reservedWord.containsKey(cmd.trim())) { + + reservedWord.get(cmd.trim()).execute(); + continue; + } + + String result = this.shellProvider.execute(cmd); + if (null != result) { + if (result.startsWith("{") || result.startsWith("[")) { + RUNTIME.getPrinter().println(JsonUtil.format(result)); + } else { + RUNTIME.getPrinter().println(result); + } + + } + } catch (Exception e) { + RUNTIME.getPrinter().println("shell evaluate fail:" + e.getMessage()); + } + } + }, "shell_thread").start(); + + } + } finally { + shellLock.unlock(); + } + } + + public String execute(String cmd) { + return this.shellProvider.execute(cmd); + } + + public void stop() { + + loopRunning.set(false); + try { + if (null != RUNTIME.getGrpcClient()) { + RUNTIME.getGrpcClient().shutdown(); + } + } catch (InterruptedException e) { + // not process + } + } + + protected void initReservedWord() { + this.reservedWord.put("exit", this::exit); + this.reservedWord.put("help", this::help); + } + + protected void exit() { + stop(); + } + + protected void help() { + RUNTIME.getPrinter().print(namespaceManager.dump()); + } + + protected void welcome() { + RUNTIME.printer.println( + " ___ __ ______ __ _ ____ _ __\n" + + " / | ____ / /_ / ____// /_ ____ _ (_)____ / __ ) _____ (_)____/ /____ _ ___\n" + + " / /| | / __ \\ / __// / / __ \\ / __ `// // __ \\ / __ |/ ___// // __ // __ `// _ \\\n" + + " / ___ | / / / // /_ / /___ / / / // /_/ // // / / / / /_/ // / / // /_/ // /_/ // __/\n" + + "/_/ |_|/_/ /_/ \\__/ \\____//_/ /_/ \\__,_//_//_/ /_/ /_____//_/ /_/ \\__,_/ \\__, / \\___/\n" + + " /____/ \n" + + " PLUGIN SERVER CLI " + Launcher.getVersion() + ); + RUNTIME.printer.println("\n>>> type help to see all commands..."); + } + + public static class Runtime { + + private PrintWriter printer; + + private ManagementGrpcClient grpcClient; + + void setPrinter(PrintWriter printer) { + + this.printer = printer; + } + + void setGrpcClient(ManagementGrpcClient grpcClient) { + this.grpcClient = grpcClient; + } + + public PrintWriter getPrinter() { + return printer; + } + + public ManagementGrpcClient getGrpcClient() { + return grpcClient; + } + + } +} diff --git a/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/shell/ShellProvider.java b/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/shell/ShellProvider.java new file mode 100644 index 0000000..3257fca --- /dev/null +++ b/ps-cli/src/main/java/com/alipay/antchain/bridge/pluginserver/cli/shell/ShellProvider.java @@ -0,0 +1,32 @@ +/* + * Copyright 2023 Ant Group + * + * Licensed 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 com.alipay.antchain.bridge.pluginserver.cli.shell; + +public interface ShellProvider { + + /** + * 执行命令 + * + * @param cmd + */ + String execute(String cmd); + + /** + * shutdown + */ + void shutdown(); +} diff --git a/ps-cli/src/main/resources/VERSION b/ps-cli/src/main/resources/VERSION new file mode 100644 index 0000000..d519d03 --- /dev/null +++ b/ps-cli/src/main/resources/VERSION @@ -0,0 +1 @@ +@project.version@ \ No newline at end of file diff --git a/ps-cli/src/main/resources/logback.xml b/ps-cli/src/main/resources/logback.xml new file mode 100644 index 0000000..f4dc85b --- /dev/null +++ b/ps-cli/src/main/resources/logback.xml @@ -0,0 +1,16 @@ + + + + + + + + %msg%n + + + + + + + + \ No newline at end of file diff --git a/ps-cli/src/main/resources/scripts/start.sh b/ps-cli/src/main/resources/scripts/start.sh new file mode 100755 index 0000000..aee2109 --- /dev/null +++ b/ps-cli/src/main/resources/scripts/start.sh @@ -0,0 +1,61 @@ +#!/bin/bash + +bin=`dirname "${BASH_SOURCE-$0}"` +CLI_HOME=`cd "$bin"; pwd` + +which java > /dev/null +if [ $? -eq 1 ]; then + echo "no java installed. " + exit 1 +fi + +Help=$(cat <<-"HELP" + + start.sh — Start the CLI tool to manage your plugin server + + Usage: + start.sh + + Examples: + 1. print help info: + start.sh -h + 2. identify the port number to start CLI + start.sh -p 9091 + 3. identify the server IP to start CLI + start.sh -H 0.0.0.0 + + Options: + -h print help info + -p identify the port number to start CLI, default 9091 + -H identify the server IP to start CLI, default 0.0.0.0 + +HELP +) + +while getopts "hH:p:" opt +do + case "$opt" in + "h") + echo "$Help" + exit 0 + ;; + "H") + HOST_IP="${OPTARG}" + ;; + "p") + PORT=${OPTARG} + ;; + "?") + echo "invalid arguments. " + exit 1 + ;; + *) + echo "Unknown error while processing options" + exit 1 + ;; + esac +done + +JAR_FILE=`ls ${CLI_HOME}/../lib` + +java -jar ${CLI_HOME}/../lib/${JAR_FILE} -p ${PORT:-9091} -H ${HOST_IP:-0.0.0.0} \ No newline at end of file diff --git a/ps-pluginmanager/pom.xml b/ps-pluginmanager/pom.xml index 02f7264..71b7cfa 100644 --- a/ps-pluginmanager/pom.xml +++ b/ps-pluginmanager/pom.xml @@ -6,7 +6,7 @@ com.alipay.antchain.bridge antchain-bridge-pluginserver - 0.1.2-SNAPSHOT + 0.2.0 ps-pluginmanager diff --git a/ps-pluginmanager/src/main/java/com/alipay/antchain/bridge/pluginserver/pluginmanager/PluginManagerWrapperImpl.java b/ps-pluginmanager/src/main/java/com/alipay/antchain/bridge/pluginserver/pluginmanager/PluginManagerWrapperImpl.java index aa5344f..374f049 100644 --- a/ps-pluginmanager/src/main/java/com/alipay/antchain/bridge/pluginserver/pluginmanager/PluginManagerWrapperImpl.java +++ b/ps-pluginmanager/src/main/java/com/alipay/antchain/bridge/pluginserver/pluginmanager/PluginManagerWrapperImpl.java @@ -116,6 +116,6 @@ public boolean hasDomain(String domain) { @Override public List allRunningDomains() { - return manager.allRunningDomains().stream().map(d -> d.toString()).collect(Collectors.toList()); + return manager.allRunningDomains().stream().map(CrossChainDomain::toString).collect(Collectors.toList()); } } diff --git a/ps-server/pom.xml b/ps-server/pom.xml index 27d33aa..b2a46ef 100644 --- a/ps-server/pom.xml +++ b/ps-server/pom.xml @@ -6,7 +6,7 @@ com.alipay.antchain.bridge antchain-bridge-pluginserver - 0.1.2-SNAPSHOT + 0.2.0 ps-server diff --git a/ps-server/src/main/java/com/alipay/antchain/bridge/pluginserver/server/CrossChainServiceImpl.java b/ps-server/src/main/java/com/alipay/antchain/bridge/pluginserver/server/CrossChainServiceImpl.java index 6dbb01c..8e441d0 100644 --- a/ps-server/src/main/java/com/alipay/antchain/bridge/pluginserver/server/CrossChainServiceImpl.java +++ b/ps-server/src/main/java/com/alipay/antchain/bridge/pluginserver/server/CrossChainServiceImpl.java @@ -33,11 +33,12 @@ import org.springframework.beans.factory.annotation.Autowired; import java.util.stream.Collectors; +import javax.annotation.Resource; @GrpcService @Slf4j public class CrossChainServiceImpl extends CrossChainServiceGrpc.CrossChainServiceImplBase { - @Autowired + @Resource private IPluginManagerWrapper pluginManagerWrapper; @Override diff --git a/ps-server/src/main/java/com/alipay/antchain/bridge/pluginserver/server/PluginManagementServiceImpl.java b/ps-server/src/main/java/com/alipay/antchain/bridge/pluginserver/server/PluginManagementServiceImpl.java index 98aa7ac..3df64fc 100644 --- a/ps-server/src/main/java/com/alipay/antchain/bridge/pluginserver/server/PluginManagementServiceImpl.java +++ b/ps-server/src/main/java/com/alipay/antchain/bridge/pluginserver/server/PluginManagementServiceImpl.java @@ -16,6 +16,8 @@ package com.alipay.antchain.bridge.pluginserver.server; +import cn.hutool.core.util.ObjectUtil; +import com.alipay.antchain.bridge.plugins.spi.bbc.IBBCService; import com.alipay.antchain.bridge.pluginserver.managementservice.*; import com.alipay.antchain.bridge.pluginserver.pluginmanager.IPluginManagerWrapper; import com.alipay.antchain.bridge.pluginserver.server.exception.ServerErrorCodeEnum; @@ -165,10 +167,17 @@ private ManageResponse handleReloadPluginInNewPath(String product, String path) * return whether the plugins of the products are supported * */ + @Override public void hasPlugins(HasPluginsRequest request, StreamObserver responseObserver) { - responseObserver.onNext(ResponseBuilder.buildHasPluginsResp(HasPluginsResp.newBuilder() - .putAllResults(request.getProductsList().stream().distinct().collect(Collectors.toMap(p -> p, p -> pluginManagerWrapper.hasPlugin(p)))) - ) + responseObserver.onNext( + ResponseBuilder.buildHasPluginsResp( + HasPluginsResp.newBuilder() + .putAllResults( + request.getProductsList().stream() + .distinct() + .collect(Collectors.toMap(p -> p, p -> pluginManagerWrapper.hasPlugin(p))) + ) + ) ); responseObserver.onCompleted(); } @@ -178,6 +187,7 @@ public void hasPlugins(HasPluginsRequest request, StreamObserver * return all supported plugin products * */ + @Override public void allPlugins(AllPluginsRequest request, StreamObserver responseObserver) { responseObserver.onNext( ResponseBuilder.buildAllPluginsResp( @@ -192,6 +202,7 @@ public void allPlugins(AllPluginsRequest request, StreamObserver * return whether the chains of the domains are running * */ + @Override public void hasDomains(HasDomainsRequest request, StreamObserver responseObserver) { responseObserver.onNext(ResponseBuilder.buildHasDomainsResp(HasDomainsResp.newBuilder() .putAllResults(request.getDomainsList().stream().distinct().collect(Collectors.toMap(d -> d, d -> pluginManagerWrapper.hasDomain(d)))) @@ -205,6 +216,7 @@ public void hasDomains(HasDomainsRequest request, StreamObserver * return domains of all running chains * */ + @Override public void allDomains(AllDomainsRequest request, StreamObserver responseObserver) { responseObserver.onNext( ResponseBuilder.buildAllDomainsResp( @@ -214,4 +226,42 @@ public void allDomains(AllDomainsRequest request, StreamObserver responseObserver.onCompleted(); } + @Override + public void restartBBC(RestartBBCRequest request, StreamObserver responseObserver) { + if (!pluginManagerWrapper.hasPlugin(request.getProduct())) { + responseObserver.onNext( + ResponseBuilder.buildFailManageResp(ServerErrorCodeEnum.MANAGE_RESTART_BBC_ERROR, "product not found") + ); + responseObserver.onCompleted(); + return; + } + if (!pluginManagerWrapper.hasDomain(request.getDomain())) { + responseObserver.onNext( + ResponseBuilder.buildFailManageResp(ServerErrorCodeEnum.MANAGE_RESTART_BBC_ERROR, "domain not found") + ); + responseObserver.onCompleted(); + return; + } + try { + IBBCService oldBbcService = pluginManagerWrapper.getBBCService(request.getProduct(), request.getDomain()); + if (ObjectUtil.isNull(oldBbcService)) { + throw new RuntimeException("null BBC service for domain " + request.getDomain()); + } + IBBCService newBbcService = pluginManagerWrapper.createBBCService(request.getProduct(), request.getDomain()); + newBbcService.startup(oldBbcService.getContext()); + } catch (Exception e) { + log.error("restartBBC fail [errorCode: {}, errorMsg: {}]", + ServerErrorCodeEnum.MANAGE_RESTART_BBC_ERROR.getErrorCode(), + ServerErrorCodeEnum.MANAGE_RESTART_BBC_ERROR.getShortMsg(), e); + responseObserver.onNext( + ResponseBuilder.buildFailManageResp(ServerErrorCodeEnum.MANAGE_RESTART_BBC_ERROR, "UNKNOWN") + ); + responseObserver.onCompleted(); + return; + } + responseObserver.onNext( + ResponseBuilder.buildRestartBBCResp(RestartBBCResp.newBuilder()) + ); + responseObserver.onCompleted(); + } } diff --git a/ps-server/src/main/java/com/alipay/antchain/bridge/pluginserver/server/ResponseBuilder.java b/ps-server/src/main/java/com/alipay/antchain/bridge/pluginserver/server/ResponseBuilder.java index 1a57846..c224f16 100644 --- a/ps-server/src/main/java/com/alipay/antchain/bridge/pluginserver/server/ResponseBuilder.java +++ b/ps-server/src/main/java/com/alipay/antchain/bridge/pluginserver/server/ResponseBuilder.java @@ -121,6 +121,15 @@ public static ManageResponse buildAllDomainsResp(AllDomainsResp.Builder respBuil .setAllDomainsResp(respBuilder).build(); } + public static ManageResponse buildRestartBBCResp(RestartBBCResp.Builder respBuilder) { + log.debug("restart bbc service success"); + return ManageResponse.newBuilder() + .setCode(ServerErrorCodeEnum.SUCCESS.getErrorCode()) + .setErrorMsg(ServerErrorCodeEnum.SUCCESS.getShortMsg()) + .setRestartBBCResp(respBuilder) + .build(); + } + public static ManageResponse buildFailManageResp(ServerErrorCodeEnum errorCodeEnum) { return ManageResponse.newBuilder() diff --git a/ps-server/src/main/java/com/alipay/antchain/bridge/pluginserver/server/exception/ServerErrorCodeEnum.java b/ps-server/src/main/java/com/alipay/antchain/bridge/pluginserver/server/exception/ServerErrorCodeEnum.java index e66d190..48817be 100644 --- a/ps-server/src/main/java/com/alipay/antchain/bridge/pluginserver/server/exception/ServerErrorCodeEnum.java +++ b/ps-server/src/main/java/com/alipay/antchain/bridge/pluginserver/server/exception/ServerErrorCodeEnum.java @@ -80,6 +80,8 @@ public enum ServerErrorCodeEnum { MANAGE_RELOAD_PLUGIN_IN_NEW_PATH_ERROR(308, "[manage] reload plugin in new path failed"), + MANAGE_RESTART_BBC_ERROR(309, "[manage] restart bbc failed"), + UNKNOWN_ERROR(100, "unknow error"); /** diff --git a/ps-service/pom.xml b/ps-service/pom.xml index d6b0455..e13eae5 100644 --- a/ps-service/pom.xml +++ b/ps-service/pom.xml @@ -6,7 +6,7 @@ com.alipay.antchain.bridge antchain-bridge-pluginserver - 0.1.2-SNAPSHOT + 0.2.0 ps-service diff --git a/ps-service/src/main/proto/managementserver.proto b/ps-service/src/main/proto/managementserver.proto index 8c30fc9..eeff3df 100644 --- a/ps-service/src/main/proto/managementserver.proto +++ b/ps-service/src/main/proto/managementserver.proto @@ -45,6 +45,11 @@ message HasDomainsRequest{ message AllDomainsRequest{ } +message RestartBBCRequest { + string product = 1; + string domain = 2; +} + message ManageResponse { uint32 code = 1; string errorMsg = 2; @@ -54,6 +59,7 @@ message ManageResponse { AllPluginsResp allPluginsResp = 5; HasDomainsResp hasDomainsResp = 6; AllDomainsResp allDomainsResp = 7; + RestartBBCResp restartBBCResp = 8; } } @@ -81,6 +87,9 @@ message AllDomainsResp { repeated string domains = 1; } +message RestartBBCResp { +} + service ManagementService { // maintenance personnel may invoke this interface to load, unload, start, and stop plugins @@ -97,4 +106,7 @@ service ManagementService { // return domains of all running chains rpc allDomains(AllDomainsRequest) returns (ManageResponse) {} + + // restart the bbc service object from the current plugin files + rpc restartBBC(RestartBBCRequest) returns (ManageResponse) {} } \ No newline at end of file