新增 er-model 项目:

- 基于 Quasar + Vue 的 ER 图可视化插件
- 支持 DBML 转 SQL、反向解析数据库结构
- 与 openGauss DataKit 后端集成,实现数据库建模与可视化
This commit is contained in:
wangziqi 2025-10-29 20:44:15 +08:00
parent d07eb9a8ec
commit dbfbf64096
2107 changed files with 238709 additions and 0 deletions

4
ER-Model/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/output/
/.idea/
/agent/
/.DS_Store/.DS_Store

194
ER-Model/README.md Normal file
View File

@ -0,0 +1,194 @@
# ER-Model 插件
ER-Model 提供一个基于 Web 的 ER 模型设计器,支持 DBML 双向转换、实例元数据导入、可视化编辑与 SQL 执行。
## 功能特性
- **DBML 编辑 / 可视化联动**:左侧编辑器实时解析 DBML右侧图形视图同步渲染表与关系。
- **实例连接与 Schema 管理**支持平台内已托管实例的选择、Schema 查询与创建、连接测试。
- **导入导出**:可从 SQL 文件或数据库中加载结构;支持导出 SQL、PlantUML、PNG / PDF 等格式。
- **自动布局与可视化优化**:内置多种布局方式、列 / 关系高亮、提示信息等交互体验。
- **SQL 执行**:可将当前 DBML 转换的 DDL 直接执行到选定实例 / Schema。
---
## 目录结构
```
plugins/er-model
├── src/main/java/org/opengauss/admin/plugin # 插件后端控制器、服务、SQL 生成、翻译等
│ ├── controller # REST 接口执行、导入导出、SPA 前置等
│ ├── dto # 数据传输对象(表/列/索引/实例请求等)
│ ├── service # 实例管理、SQL 执行、导出等业务服务
│ ├── sql # 方言适配的 SQL 生成器
│ └── translate # DBML → DTO / SQL 转换逻辑
├── src/main/resources # 插件资源文件
│ └── resources # 构建产物:前端静态资源、国际化等
└── web-ui # 前端工程Quasar + Vue3
├── src/components # 组件:图形视图、表格节点、工具提示等
├── src/pages # 页面:编辑器、只读展示、错误页等
├── src/store # Pinia 数据仓库:编辑器状态、图形状态等
├── src/utils # 工具:导出、布局、下载、认证等
└── src/css # 样式ER 图主题、全局样式
```
---
# er-model 安装与运行指南
## 一、环境配置
### 1. 安装 openGauss 数据库
请先安装并启动 openGauss 数据库。
> **参考文档:** [openGauss 参数配置说明](https://gitcode.com/opengauss/openGauss-workbench#补充opengauss参数配置)
> (特别注意数据库远程用户访问权限的设置)
### 2. 安装依赖环境
确保已安装以下组件:
- Java 17+
- Maven 3.9.0+
- Node.js v18+(含 npm
并配置好:
- Maven 镜像源
- Node 镜像源
### 3. 安装 SpringBrick 组件
在本地编译并安装:
[springboot-plugin-framework-parent](https://gitcode.com/wang4721/springboot-plugin-framework-parent.git)
---
## 二、开发环境运行
### 构建步骤
1. 修改 `openGauss-datakit/visualtool-api` 目录下的 `application-dev.yml`,配置数据库连接、用户名与密码(需为远程用户)。
2. 在项目根目录创建文件夹:
```bash
mkdir visualtool-plugin
```
用于存放构建后的插件 jar 包。
3. 执行构建脚本:
```bash
sh idea-debug-plugins-api-build.sh
```
4. 将 `base-ops``er-model` 模块中 `target` 目录下的 jar 包拷贝至 `visualtool-plugin` 文件夹。
5. 启动主应用类:
```
openGauss-datakit/visualtool-api/src/main/java/org/opengauss/admin/AdminApplication.java
```
### 启动与访问
- 开发环境一般不启用 SSL可直接使用 HTTP 访问:
```
http://ip:9494/
```
- 默认账号密码:
```
用户名admin
密码admin123
```
首次登录需修改密码。
---
## 三、服务器环境运行
1. **构建安装包**
```bash
sh build.sh
```
成功后会生成:
```
openGauss-Datakit-All-7.0.0-RC3.tar.gz
```
将该文件上传至服务器。
2. **解压安装包**
```bash
tar -xzf openGauss-Datakit-All-7.0.0-RC3.tar.gz
cd openGauss-datakit
```
3. **创建目录**
```bash
mkdir config files ssl logs
```
4. **修改配置文件 - 工作目录**
编辑 `application-temp.yml`
```yaml
system.defaultStoragePath: /ops/files
server.ssl.key-store: /ops/ssl/keystore.p12
logging.file.path: /ops/logs
```
`/ops` 替换为实际安装目录(如 `/path/datakit_server`
然后将文件移动至 `config` 目录:
```bash
mv application-temp.yml config/
```
5. **修改配置文件 - 数据库连接**
如使用 openGauss 数据库:
```yaml
driver-class-name: org.opengauss.Driver
url: jdbc:opengauss://ip:port/database?currentSchema=public&batchMode=off
username: dbuser
password: ******
```
6. **生成 SSL 密钥**
```bash
keytool -genkey -noprompt -dname "CN=opengauss, OU=opengauss, O=opengauss, L=Beijing, S=Beijing, C=CN" -alias opengauss -storetype PKCS12 -keyalg RSA -keysize 4096 -keystore /path/datakit_server/ssl/keystore.p12 -validity 365 -storepass ****** -ext "SAN=IP:x.x.x.x"
```
- `-storepass` 值需与配置文件中 `server.ssl.key-store-password` 保持一致
- `x.x.x.x` 替换为服务器实际 IP
7. **启动与运维**
```bash
sh ./run.sh start --aes-key xxxxxx # 启动
sh ./run.sh stop # 停止
sh ./run.sh restart --aes-key xxxxxx # 重启
sh ./run.sh status # 查看状态
```
8. **访问服务**
启动成功后,通过浏览器访问:
```
https://ip:9494/
```
默认账号密码:
```
用户名admin
密码admin123
```
---
## 四、补充说明
- 若使用 openGauss 作为后台数据库,请确保已正确配置远程连接参数。
- 详细参数说明可参考:[openGauss 参数配置文档](https://gitcode.com/opengauss/openGauss-workbench#补充opengauss参数配置)。
---
## License
本插件遵循 [Mulan PSL v2](http://license.coscl.org.cn/MulanPSL2) 协议。

236
ER-Model/build.sh Normal file
View File

@ -0,0 +1,236 @@
#!/bin/bash
#############################################################################
# Copyright (c) 2023 Huawei Technologies Co.,Ltd.
#
# openGauss is licensed under Mulan PSL v2.
# You can use this software according to the terms
# and conditions of the Mulan PSL v2.
# You may obtain a copy of Mulan PSL v2 at:
#
# http://license.coscl.org.cn/MulanPSL2
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
# See the Mulan PSL v2 for more details.
# ----------------------------------------------------------------------------
# Description : shell script for build datakit and plugins.
#############################################################################
declare mvn_target="clean package"
declare mvn_input_args=''
declare mvn_prod='prod'
#############################################################################
function print_help()
{
echo "Usage: $0 [OPTION]
-h|--help show help information
-t|--target set the mvn build target, default is clean package
-a|--args set the mvn build args additions, default is empty
-p|--profile the profile, default is prod
example:
sh build.sh // build in default
sh build.sh -a \"-Dbuild.frontend.skip=true -Dweb.build.skip=true\" // build skip frontend
sh build.sh -t \"clean\" -a \"-Dbuild.frontend.skip=true -Dweb.build.skip=true\" // build only clean and skip frontend
"
}
while [ $# -gt 0 ]; do
case "$1" in
-h|--help)
print_help
exit 1
;;
-t|--target)
if [ "$2"X = X ]; then
echo "no given correct mvn target, such as: clean package"
exit 1
fi
mvn_target=$2
shift 2
;;
-a|--args)
if [ "$2"X = X ]; then
echo "no given correct mvn args, such as: -Dbuild.frontend.skip=true -Dweb.build.skip=true"
exit 1
fi
mvn_input_args=$2
shift 2
;;
-p|--profile)
if [ "$2"X = X ]; then
echo "no given mvn profile, such as release or dev"
exit 1
fi
mvn_prod=$2
shift 2
;;
*)
echo "Internal Error: option processing error: $1" 1>&2
echo "please input right paramtenter, the following command may help you"
echo "./build.sh --help or ./build.sh -h"
exit 1
esac
done
root_path=`pwd`
output_path=$root_path/output
mvn_init_args="-Dmaven.test.skip=true"
mvn_args="$mvn_init_args -U -P $mvn_prod $mvn_input_args"
build_main_pkg=openGauss-datakit
plugin_output=visualtool-plugin
plugin_doc_output=doc
echo "we got build cmd: mvn $mvn_target $mvn_args"
export JAVA_TOOL_OPTIONS="-Dfile.encoding=UTF8"
pom_version=`awk '/<admin.version>[^<]+<\/admin.version>/{gsub(/<admin.version>|<\/admin.version>/,"",$1);print $1;exit;}' ${root_path}/pom.xml`
mkdir -p output
rm -rf output/*
mkdir -p output/$plugin_output
mkdir -p output/$plugin_doc_output
function prepare_java_env()
{
echo "We no longer provide java, please makesure java(11+) already in PATH!"
JAVA_VERSION=`java -version 2>&1 | awk -F '"' '/version/ {print $2}'`
if [ -z "$JAVA_VERSION" ]; then
echo "Failed to obtain java version!"
exit 1
fi
echo java version is $JAVA_VERSION
MAJOR_VERSION=`echo $JAVA_VERSION | awk -F '[.]' '{print $1}'`
if [ $MAJOR_VERSION -lt 11 ]; then
echo "java version is not meeting requirements!"
exit 1
fi
}
function prepare_maven_env()
{
echo "We no longer provide mvn, please makesure mvn(3.6.0+) already in PATH!"
MAVEN_VERSION=`mvn -v 2>&1 | awk '/Apache Maven / {print $3}'`
if [ -z "$MAVEN_VERSION" ]; then
echo "Failed to obtain maven version!"
exit 1
fi
echo maven version is $MAVEN_VERSION
read MAJOR_VERSION MINOR_VERSION <<< $(echo $MAVEN_VERSION | awk -F '[.]' '{print $1, $2}')
if [ $MAJOR_VERSION -lt 3 ] || ([ $MAJOR_VERSION -eq 3 ] && [ $MINOR_VERSION -lt 6 ]); then
echo "maven version is not meeting requirements!"
exit 1
fi
}
function prepare_npm_env()
{
echo "We no longer provide node/npm, please makesure npm(8.11.0+) already in PATH!"
NPM_VERSION=`npm -v 2>&1 | awk -F '"' '// {print $1}'`
if [ -z "$NPM_VERSION" ]; then
echo "Failed to obtain npm version!"
exit 1
fi
echo npm version is $NPM_VERSION
read MAJOR_VERSION MINOR_VERSION <<< $(echo $NPM_VERSION | awk -F '[.]' '{print $1, $2}')
if [ $MAJOR_VERSION -lt 8 ] || ([ $MAJOR_VERSION -eq 8 ] && [ $MINOR_VERSION -lt 11 ]); then
echo "npm version is not meeting requirements!"
exit 1
fi
}
function prepare_env()
{
prepare_java_env
prepare_maven_env
prepare_npm_env
}
function get_git_log(){
cd $output_path
package_time=`date '+%Y-%m-%d %H:%M:%S'`
echo "--------------------------------get_git_log---------------------------------"
echo "build time: "$package_time
echo "build time: "$package_time >> build_commit_id.log
echo "git branch: "$(git rev-parse --abbrev-ref HEAD)
echo "git branch: "$(git rev-parse --abbrev-ref HEAD) >> build_commit_id.log
echo "last commit:"
echo "last commit:" >> build_commit_id.log
echo "$(git log -1)"
echo "$(git log -1)" >> build_commit_id.log
echo "--------------------------------get_git_log finished---------------------------------"
}
function fetch_and_build_install_spring_brick() {
cd $root_path
git clone https://gitcode.com/wang4721/springboot-plugin-framework-parent.git
cd springboot-plugin-framework-parent
echo "build dir:${root_path} ,to run cmd: mvn clean install -Dmaven.test.skip=true"
mvn clean install -Dmaven.test.skip=true
if [ $? -ne 0 ]; then
echo "Build spring brick failed..."
exit 1
fi
cd ..
rm -rf springboot-plugin-framework-parent
}
function build_pkg() {
cd $root_path
echo "build dir:${root_path} ,to run cmd: mvn ${mvn_target} ${mvn_args}"
mvn --threads=4C ${mvn_target} ${mvn_args}
if [ $? -ne 0 ]; then
echo "Build datakit failed..."
exit 1
fi
cp ./${build_main_pkg}/visualtool-api/target/openGauss-datakit-*.jar ${output_path}/
cp ./run.sh ${output_path}/
cp ./${build_main_pkg}/README.md ${output_path}/${plugin_doc_output}/datakit-README.md
cp ./${build_main_pkg}/config/application-temp.yml ${output_path}/
}
function copy_plugin_pkg() {
local plugin_path=$root_path/plugins
cd $root_path
sub_plugin_paths=`ls -D ./plugins`
for plugin_name in ${sub_plugin_paths[*]}
do
{
local build_path=$plugin_path/${plugin_name}
cd ${build_path}
if [ "$plugin_name" == "openGauss-tools-monitor" ] ; then
cp ./**/target/*repackage.jar ${output_path}/$plugin_output/ 2>/dev/null
echo "copy ${plugin_name} success!!!"
elif [ "$plugin_name" == "container-management-plugin" ]; then
continue
else
cp ./**target/*repackage.jar ${output_path}/$plugin_output/ 2>/dev/null
echo "copy ${plugin_name} success!!!"
fi
if [ -f ./readme.md ] ; then
cp ./readme.md ${output_path}/${plugin_doc_output}/${plugin_name}-README.md
fi
if [ -f ./README.md ] ; then
cp ./README.md ${output_path}/${plugin_doc_output}/${plugin_name}-README.md
fi
}&
done
wait
}
prepare_env
#fetch_and_build_install_spring_brick
#get_git_log
#build_pkg
copy_plugin_pkg
cd $output_path
tar -zcf openGauss-Datakit-All-${pom_version}.tar.gz ./*
tar -zcf openGauss-Datakit-Mini-${pom_version}.tar.gz \
./application-temp.yml \
./build_commit_id.log \
./doc \
./openGauss-datakit* \
./run.sh \
./visualtool-plugin/webds-plugin* \
./visualtool-plugin/base-ops* \
./agent/*

Binary file not shown.

View File

@ -0,0 +1,45 @@
#!/bin/bash
#############################################################################
# Copyright (c) 2023 Huawei Technologies Co.,Ltd.
#
# openGauss is licensed under Mulan PSL v2.
# You can use this software according to the terms
# and conditions of the Mulan PSL v2.
# You may obtain a copy of Mulan PSL v2 at:
#
# http://license.coscl.org.cn/MulanPSL2
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
# See the Mulan PSL v2 for more details.
# ----------------------------------------------------------------------------
# Description : shell script for build datakit and plugins.
#############################################################################
mvn --threads=4C clean
# rm -rf /Users/wangziqi/.m2/repository/*
#mvn --threads=4C clean install -Dbuild.frontend.skip=true -Dweb.build.skip=true -Dmaven.test.skip=true
#mvn --threads=1C -T 1 clean install -Dmaven.test.skip=true
mvn clean install -Dmaven.test.skip=true
version_num=7.0.0-RC3
rm -rf visualtool-plugin/*
rm -rf openGauss-datakit-${version_num}.jar
cp plugins/base-ops/target/base-ops-${version_num}-repackage.jar visualtool-plugin/
cp plugins/er-model/target/er-model-${version_num}-repackage.jar visualtool-plugin/
#function fetch_build_install_spring_brick() {
# cd $root_path
# git clone https://gitcode.com/wang4721/springboot-plugin-framework-parent.git
# cd springboot-plugin-framework-parent
# echo "build dir:${root_path} ,to run cmd: mvn clean install -Dmaven.test.skip=true"
# mvn clean install -Dmaven.test.skip=true
# if [ $? -ne 0 ]; then
# echo "Build spring brick failed..."
# exit 1
# fi
# cd ..
# rm -rf springboot-plugin-framework-parent
#}

View File

@ -0,0 +1,5 @@
*
!target/*-runner
!target/*-runner.jar
!target/lib/*
!target/quarkus-app/

View File

@ -0,0 +1,35 @@
# Eclipse
.project
.classpath
.settings/
bin/
# IntelliJ
.idea
*.ipr
*.iml
*.iws
# NetBeans
nb-configuration.xml
# Visual Studio Code
.vscode
# OSX
.DS_Store
# Vim
*.swp
*.swo
# patch
*.orig
*.rej
# Maven
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
release.properties

View File

@ -0,0 +1,79 @@
# Getting started with Quarkus
This is a minimal CRUD service exposing a couple of endpoints over REST.
Under the hood, this demo uses:
- RESTEasy to expose the REST endpoints
- REST-assured and JUnit 5 for endpoint testing
## Requirements
To compile and run this demo you will need:
- JDK 17+
- GraalVM
### Configuring GraalVM and JDK 17+
Make sure that both the `GRAALVM_HOME` and `JAVA_HOME` environment variables have
been set, and that a JDK 17+ `java` command is on the path.
See the [Building a Native Executable guide](https://quarkus.io/guides/building-native-image-guide)
for help setting up your environment.
## Building the application
Launch the Maven build on the checked out sources of this demo:
> ./mvnw package
### Live coding with Quarkus
The Maven Quarkus plugin provides a development mode that supports
live coding. To try this out:
> ./mvnw quarkus:dev
This command will leave Quarkus running in the foreground listening on port 8080.
1. Visit the default endpoint: [http://127.0.0.1:8080](http://127.0.0.1:8080).
- Make a simple change to [src/main/resources/META-INF/resources/index.html](src/main/resources/META-INF/resources/index.html) file.
- Refresh the browser to see the updated page.
2. Visit the `/hello` endpoint: [http://127.0.0.1:8080/hello](http://127.0.0.1:8080/hello)
- Update the response in [src/main/java/org/acme/quickstart/GreetingResource.java](src/main/java/org/acme/quickstart/GreetingResource.java). Replace `hello` with `hello there` in the `hello()` method.
- Refresh the browser. You should now see `hello there`.
- Undo the change, so the method returns `hello` again.
- Refresh the browser. You should now see `hello`.
### Run Quarkus in JVM mode
When you're done iterating in developer mode, you can run the application as a
conventional jar file.
First compile it:
> ./mvnw package
Then run it:
> java -jar ./target/quarkus-app/quarkus-run.jar
Have a look at how fast it boots, or measure the total native memory consumption.
### Run Quarkus as a native executable
You can also create a native executable from this application without making any
source code changes. A native executable removes the dependency on the JVM:
everything needed to run the application on the target platform is included in
the executable, allowing the application to run with minimal resource overhead.
Compiling a native executable takes a bit longer, as GraalVM performs additional
steps to remove unnecessary codepaths. Use the `native` profile to compile a
native executable:
> ./mvnw package -Dnative
After getting a cup of coffee, you'll be able to run this executable directly:
> ./target/getting-started-1.0.0-SNAPSHOT-runner

View File

@ -0,0 +1,337 @@
<?xml version="1.0" encoding="UTF-8"?>
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>org.opengauss</groupId>
<artifactId>openGauss-datakit-agent</artifactId>
<version>7.0.0-RC3</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>17</java.version>
<quarkus.package.type>uber-jar</quarkus.package.type>
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
<quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
<quarkus.platform.version>3.26.4</quarkus.platform.version>
<retrofit.version>3.0.0</retrofit.version>
<oshi.version>6.4.6</oshi.version>
<common.lang3.version>3.18.0</common.lang3.version>
<commons.exec.version>1.5.0</commons.exec.version>
<lombok.version>1.18.34</lombok.version>
<hutool.version>5.8.38</hutool.version>
<hikaricp.version>7.0.0</hikaricp.version>
<opengauss-jdbc.version>6.0.0-og</opengauss-jdbc.version>
<mysql-jdbc.version>9.4.0</mysql-jdbc.version>
<jna.version>5.13.0</jna.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<maven-clean-plugin.version>3.2.0</maven-clean-plugin.version>
<maven-compiler-plugin.version>3.11.0</maven-compiler-plugin.version>
<maven-surefire-plugin.version>3.0.0-M7</maven-surefire-plugin.version>
<maven.compiler.parameters>true</maven.compiler.parameters>
<skipITs>true</skipITs>
</properties>
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.quarkus.platform</groupId>
<artifactId>quarkus-bom</artifactId>
<version>${quarkus.platform.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-vertx</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-messaging</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus.resteasy.reactive</groupId>
<artifactId>resteasy-reactive-common</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-mutiny</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-client</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-jackson</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-client-jackson</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-config-yaml</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-scheduler</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-logging</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-api</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-context-propagation</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc</artifactId>
</dependency>
<dependency>
<groupId>com.github.oshi</groupId>
<artifactId>oshi-core</artifactId>
<version>${oshi.version}</version>
</dependency>
<dependency>
<groupId>com.squareup.retrofit2</groupId>
<artifactId>retrofit</artifactId>
<version>${retrofit.version}</version>
</dependency>
<dependency>
<groupId>com.squareup.retrofit2</groupId>
<artifactId>converter-jackson</artifactId>
<version>${retrofit.version}</version>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna-platform</artifactId>
<version>${jna.version}</version>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>${jna.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${common.lang3.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>${hikaricp.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>${mysql-jdbc.version}</version>
</dependency>
<dependency>
<groupId>org.opengauss</groupId>
<artifactId>opengauss-jdbc</artifactId>
<version>${opengauss-jdbc.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-exec</artifactId>
<version>${commons.exec.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<compilerArguments>
<bootclasspath>${java.home}/lib/rt.jar</bootclasspath>
</compilerArguments>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
<configuration>
<systemPropertyVariables>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
</configuration>
</plugin>
<plugin>
<groupId>org.jboss.jandex</groupId>
<artifactId>jandex-maven-plugin</artifactId>
<version>1.2.3</version>
<executions>
<execution>
<id>make-index</id>
<goals><goal>jandex</goal></goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>${quarkus.platform.group-id}</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<version>${quarkus.platform.version}</version>
<executions>
<execution>
<goals>
<goal>build</goal>
</goals>
</execution>
</executions>
<configuration>
<finalName>datakit-agent-${version}</finalName>
<outputDirectory>../agent</outputDirectory>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.3.0</version>
<executions>
<execution>
<id>copy-configs</id>
<phase>package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>../agent</outputDirectory>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>*</include>
</includes>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<phase>package</phase>
<configuration>
<target>
<copy file="${project.build.directory}/datakit-agent-${version}-runner.jar"
todir="../agent"/>
</target>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>native</id>
<activation>
<property>
<name>native</name>
</property>
</activation>
<properties>
<quarkus.native.enabled>true</quarkus.native.enabled>
<quarkus.package.jar.enabled>false</quarkus.package.jar.enabled>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
<configuration>
<systemPropertyVariables>
<native.image.path>${project.build.directory}/datakit-agent-runner</native.image.path>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@ -0,0 +1,97 @@
####
# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode
#
# Before building the container image run:
#
# ./mvnw package
#
# Then, build the image with:
#
# docker build -f src/main/docker/Dockerfile.jvm -t quarkus/getting-started-jvm .
#
# Then run the container using:
#
# docker run -i --rm -p 8080:8080 quarkus/getting-started-jvm
#
# If you want to include the debug port into your docker image
# you will have to expose the debug port (default 5005 being the default) like this : EXPOSE 8080 5005.
# Additionally you will have to set -e JAVA_DEBUG=true and -e JAVA_DEBUG_PORT=*:5005
# when running the container
#
# Then run the container using :
#
# docker run -i --rm -p 8080:8080 quarkus/getting-started-jvm
#
# This image uses the `run-java.sh` script to run the application.
# This scripts computes the command line to execute your Java application, and
# includes memory/GC tuning.
# You can configure the behavior using the following environment properties:
# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class")
# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options
# in JAVA_OPTS (example: "-Dsome.property=foo")
# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is
# used to calculate a default maximal heap memory based on a containers restriction.
# If used in a container without any memory constraints for the container then this
# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio
# of the container available memory as set here. The default is `50` which means 50%
# of the available memory is used as an upper boundary. You can skip this mechanism by
# setting this value to `0` in which case no `-Xmx` option is added.
# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This
# is used to calculate a default initial heap memory based on the maximum heap memory.
# If used in a container without any memory constraints for the container then this
# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio
# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx`
# is used as the initial heap size. You can skip this mechanism by setting this value
# to `0` in which case no `-Xms` option is added (example: "25")
# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS.
# This is used to calculate the maximum value of the initial heap memory. If used in
# a container without any memory constraints for the container then this option has
# no effect. If there is a memory constraint then `-Xms` is limited to the value set
# here. The default is 4096MB which means the calculated value of `-Xms` never will
# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096")
# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output
# when things are happening. This option, if set to true, will set
# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true").
# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example:
# true").
# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787").
# - CONTAINER_CORE_LIMIT: A calculated core limit as described in
# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2")
# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024").
# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion.
# (example: "20")
# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking.
# (example: "40")
# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection.
# (example: "4")
# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus
# previous GC times. (example: "90")
# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20")
# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100")
# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should
# contain the necessary JRE command-line options to specify the required GC, which
# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC).
# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080")
# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080")
# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be
# accessed directly. (example: "foo.example.com,bar.example.com")
#
###
FROM registry.access.redhat.com/ubi8/openjdk-17:1.20
ENV LANGUAGE='en_US:en'
# We make four distinct layers so if there are application changes the library layers can be re-used
COPY --chown=185 target/quarkus-app/lib/ /deployments/lib/
COPY --chown=185 target/quarkus-app/*.jar /deployments/
COPY --chown=185 target/quarkus-app/app/ /deployments/app/
COPY --chown=185 target/quarkus-app/quarkus/ /deployments/quarkus/
EXPOSE 8080
USER 185
ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ]

View File

@ -0,0 +1,93 @@
####
# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode
#
# Before building the container image run:
#
# ./mvnw package -Dquarkus.package.jar.type=legacy-jar
#
# Then, build the image with:
#
# docker build -f src/main/docker/Dockerfile.legacy-jar -t quarkus/getting-started-legacy-jar .
#
# Then run the container using:
#
# docker run -i --rm -p 8080:8080 quarkus/getting-started-legacy-jar
#
# If you want to include the debug port into your docker image
# you will have to expose the debug port (default 5005 being the default) like this : EXPOSE 8080 5005.
# Additionally you will have to set -e JAVA_DEBUG=true and -e JAVA_DEBUG_PORT=*:5005
# when running the container
#
# Then run the container using :
#
# docker run -i --rm -p 8080:8080 quarkus/getting-started-legacy-jar
#
# This image uses the `run-java.sh` script to run the application.
# This scripts computes the command line to execute your Java application, and
# includes memory/GC tuning.
# You can configure the behavior using the following environment properties:
# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class")
# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options
# in JAVA_OPTS (example: "-Dsome.property=foo")
# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is
# used to calculate a default maximal heap memory based on a containers restriction.
# If used in a container without any memory constraints for the container then this
# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio
# of the container available memory as set here. The default is `50` which means 50%
# of the available memory is used as an upper boundary. You can skip this mechanism by
# setting this value to `0` in which case no `-Xmx` option is added.
# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This
# is used to calculate a default initial heap memory based on the maximum heap memory.
# If used in a container without any memory constraints for the container then this
# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio
# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx`
# is used as the initial heap size. You can skip this mechanism by setting this value
# to `0` in which case no `-Xms` option is added (example: "25")
# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS.
# This is used to calculate the maximum value of the initial heap memory. If used in
# a container without any memory constraints for the container then this option has
# no effect. If there is a memory constraint then `-Xms` is limited to the value set
# here. The default is 4096MB which means the calculated value of `-Xms` never will
# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096")
# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output
# when things are happening. This option, if set to true, will set
# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true").
# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example:
# true").
# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787").
# - CONTAINER_CORE_LIMIT: A calculated core limit as described in
# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2")
# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024").
# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion.
# (example: "20")
# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking.
# (example: "40")
# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection.
# (example: "4")
# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus
# previous GC times. (example: "90")
# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20")
# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100")
# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should
# contain the necessary JRE command-line options to specify the required GC, which
# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC).
# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080")
# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080")
# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be
# accessed directly. (example: "foo.example.com,bar.example.com")
#
###
FROM registry.access.redhat.com/ubi8/openjdk-17:1.20
ENV LANGUAGE='en_US:en'
COPY target/lib/* /deployments/lib/
COPY target/*-runner.jar /deployments/quarkus-run.jar
EXPOSE 8080
USER 185
ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ]

View File

@ -0,0 +1,27 @@
####
# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode.
#
# Before building the container image run:
#
# ./mvnw package -Dnative
#
# Then, build the image with:
#
# docker build -f src/main/docker/Dockerfile.native -t quarkus/getting-started .
#
# Then run the container using:
#
# docker run -i --rm -p 8080:8080 quarkus/getting-started
#
###
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.10
WORKDIR /work/
RUN chown 1001 /work \
&& chmod "g+rwX" /work \
&& chown 1001:root /work
COPY --chown=1001:root --chmod=0755 target/*-runner /work/application
EXPOSE 8080
USER 1001
ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"]

View File

@ -0,0 +1,30 @@
####
# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode.
# It uses a micro base image, tuned for Quarkus native executables.
# It reduces the size of the resulting container image.
# Check https://quarkus.io/guides/quarkus-runtime-base-image for further information about this image.
#
# Before building the container image run:
#
# ./mvnw package -Dnative
#
# Then, build the image with:
#
# docker build -f src/main/docker/Dockerfile.native-micro -t quarkus/getting-started .
#
# Then run the container using:
#
# docker run -i --rm -p 8080:8080 quarkus/getting-started
#
###
FROM quay.io/quarkus/quarkus-micro-image:2.0
WORKDIR /work/
RUN chown 1001 /work \
&& chmod "g+rwX" /work \
&& chown 1001:root /work
COPY --chown=1001:root --chmod=0755 target/*-runner /work/application
EXPOSE 8080
USER 1001
ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"]

View File

@ -0,0 +1,43 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.client;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.QueryParam;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
/**
* AgentServerClient
*
* @author: wangchao
* @Date: 2025/2/27 12:25
* @Description: AgentServerClient
* @since 7.0.0-RC2
**/
@Path("/agent")
@RegisterRestClient
public interface AgentServerClient {
/**
* task callback start
*
* @param agentId agent id
*/
@POST
@Path("/task/callback/start")
void taskCallbackStart(@QueryParam("agentId") Long agentId);
}

View File

@ -0,0 +1,58 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.client;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import org.opengauss.agent.entity.ProcessedData;
import java.util.List;
/**
* DataStreamService
*
* @author: wangchao
* @Date: 2025/2/28 09:26
* @Description: DataStreamService
* @since 7.0.0-RC2
**/
@Path("/agent")
@RegisterRestClient
public interface DataStreamService {
/**
* push data to downstream service
*
* @param data data
*/
@POST
@Path("/data")
@Produces(MediaType.APPLICATION_JSON)
void pushData(ProcessedData data);
/**
* batch push data to downstream service
*
* @param dataList data list
*/
@POST
@Path("/batch/data")
@Produces(MediaType.APPLICATION_JSON)
void batchPushData(List<ProcessedData> dataList);
}

View File

@ -0,0 +1,98 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.client;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import okhttp3.ConnectionPool;
import okhttp3.OkHttpClient;
import retrofit2.Retrofit;
import retrofit2.converter.jackson.JacksonConverterFactory;
import org.opengauss.agent.config.AgentSslContext;
import org.opengauss.agent.constant.AgentConstants;
import org.opengauss.agent.exception.AgentException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
/**
* DynamicHttpClientBuilder
*
* @author: wangchao
* @Date: 2025/5/22 10:33
* @since 7.0.0-RC2
**/
public class DynamicHttpClientBuilder {
private static final ObjectMapper MAPPER = new ObjectMapper().registerModule(new JavaTimeModule())
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.setTimeZone(TimeZone.getTimeZone("UTC")) // 全局时区
.setDateFormat(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"))
.registerModule(new SimpleModule().addSerializer(Integer.class, new ToStringSerializer()) // 将Integer序列化为字符串
.addSerializer(Integer.TYPE, new ToStringSerializer()) // 处理基本类型 Integer
.addSerializer(Long.class, new ToStringSerializer()) // 将Long序列化为字符串
.addSerializer(Long.TYPE, new ToStringSerializer())); // 处理基本类型long
/**
* createHttpClient
*
* @param baseUrl baseUrl
* @return MetricHttpClient
*/
public static MetricHttpClient createHttpClient(String baseUrl) {
Retrofit retrofit = new Retrofit.Builder().baseUrl(formatBaseUrl(baseUrl))
.client(defaultClient())
.addConverterFactory(JacksonConverterFactory.create(MAPPER))
.build();
return retrofit.create(MetricHttpClient.class);
}
private static String formatBaseUrl(String rawUrl) {
return rawUrl.endsWith("/") ? rawUrl : rawUrl + "/";
}
private static OkHttpClient defaultClient() {
OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder().connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.connectionPool(new ConnectionPool(5, 5, TimeUnit.MINUTES));
String protocol = ServerUrlBuilder.getAgentServerProtocol();
if (AgentConstants.ConfigProperties.HTTPS.equalsIgnoreCase(protocol)) {
configureTrustAll(clientBuilder);
}
return clientBuilder.build();
}
private static void configureTrustAll(OkHttpClient.Builder clientBuilder) {
try {
SSLContext sslContext = AgentSslContext.configureSslContext();
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
clientBuilder.sslSocketFactory(sslSocketFactory, AgentSslContext.getX509TrustManager());
clientBuilder.hostnameVerifier((hostname, session) -> true);
} catch (NoSuchAlgorithmException | KeyManagementException e) {
throw new AgentException("Failed to configure unsafe SSL", e);
}
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.client;
import jakarta.ws.rs.HeaderParam;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import org.opengauss.agent.entity.HeartbeatReport;
/**
* HeartbeatServerClient
*
* @author: wangchao
* @Date: 2025/2/27 12:25
* @Description: HeartbeatServerClient
* @since 7.0.0-RC2
**/
@Path("/agent")
@RegisterRestClient
public interface HeartbeatServerClient {
/**
* heartbeat
*
* @param customHeader custom header
* @param heart heartbeat report
*/
@POST
@Path("/heartbeat")
@Produces(MediaType.APPLICATION_JSON)
void heartbeat(@HeaderParam("X-Custom-Header") String customHeader, HeartbeatReport heart);
/**
* server deregister
*
* @param customHeader custom header
* @param requestDown heartbeat report
*/
@POST
@Path("/deregister")
void deregister(@HeaderParam("X-Custom-Header") String customHeader, HeartbeatReport requestDown);
}

View File

@ -0,0 +1,48 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.client;
import jakarta.ws.rs.HeaderParam;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import org.opengauss.agent.entity.HostBaseInfo;
/**
* HostFixedMetricsClient
*
* @author: wangchao
* @Date: 2025/2/27 12:25
* @Description: HostFixedMetricsClient
* @since 7.0.0-RC2
**/
@Path("/receive")
@RegisterRestClient
public interface HostFixedMetricsClient {
/**
* sendHostBaseInfo
*
* @param customHeader X-Custom-Header
* @param hostBaseInfo hostBaseInfo
*/
@POST
@Path("/fixed/host/info")
@Produces(MediaType.APPLICATION_JSON)
void sendHostBaseInfo(@HeaderParam("X-Custom-Header") String customHeader, HostBaseInfo hostBaseInfo);
}

View File

@ -0,0 +1,63 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.client;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.POST;
import retrofit2.http.Path;
import retrofit2.http.Query;
import org.opengauss.agent.entity.Metric;
import java.util.List;
import java.util.Map;
/**
* MetricHttpClient
*
* @author: wangchao
* @Date: 2025/5/16 11:41
* @Description: MetricHttpClient
* @since 7.0.0-RC2
**/
public interface MetricHttpClient {
/**
* send metrics
*
* @param path url path template, e.g. /metrics/collect
* @param taskIds task id list
* @param agentId agent id
* @param metricsPayload metrics payload
* @return void
*/
@POST("{path}")
Call<Void> sendMetrics(@Path(value = "path", encoded = true) String path, @Query("taskIds") List<Long> taskIds,
@Query("agentId") Long agentId, @Body List<Metric> metricsPayload);
/**
* send data metrics
*
* @param path url path template, e.g. /metrics/collect
* @param taskId task id
* @param agentId agent id
* @param dataList metrics payload
* @return void
*/
@POST("{path}")
Call<Void> sendDataMetrics(@Path(value = "path", encoded = true) String path, @Query("taskId") Long taskId,
@Query("agentId") Long agentId, @Body List<Map<String, Object>> dataList);
}

View File

@ -0,0 +1,87 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.client;
import jakarta.enterprise.context.ApplicationScoped;
import org.eclipse.microprofile.rest.client.RestClientBuilder;
import org.opengauss.agent.config.AgentSslContext;
import org.opengauss.agent.constant.AgentConstants;
import org.opengauss.agent.exception.AgentException;
import java.net.URI;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
/**
* ServerClientFactory
*
* @author: wangchao
* @Date: 2025/10/21 11:05
* @since 7.0.0-RC3
**/
@ApplicationScoped
public class ServerClientFactory {
/**
* create heartbeat client
*
* @return HeartbeatServerClient
*/
public HeartbeatServerClient createHeartbeatClient() {
return createClient(HeartbeatServerClient.class);
}
/**
* create agent server client
*
* @return AgentServerClient
*/
public AgentServerClient createAgentServerClient() {
return createClient(AgentServerClient.class);
}
/**
* create HostFixedMetricsClient client
*
* @return HostFixedMetricsClient
*/
public HostFixedMetricsClient createHostFixedMetricsClient() {
return createClient(HostFixedMetricsClient.class);
}
/**
* create DataStreamService client
*
* @return DataStreamService
*/
public DataStreamService createDataStreamService() {
return createClient(DataStreamService.class);
}
private <T> T createClient(Class<T> clientClass) {
try {
URI baseUri = ServerUrlBuilder.buildBaseUri();
RestClientBuilder builder = RestClientBuilder.newBuilder().baseUri(baseUri);
String protocol = ServerUrlBuilder.getAgentServerProtocol();
if (AgentConstants.ConfigProperties.HTTPS.equalsIgnoreCase(protocol)) {
builder.sslContext(AgentSslContext.configureSslContext()).hostnameVerifier((hostname, session) -> true);
}
return builder.build(clientClass);
} catch (NoSuchAlgorithmException | KeyManagementException ex) {
throw new AgentException("build agent server client error", ex);
}
}
}

View File

@ -0,0 +1,66 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.client;
import org.eclipse.microprofile.config.ConfigProvider;
import org.opengauss.agent.constant.AgentConstants;
import java.net.URI;
import java.util.Locale;
/**
* ServerUrlBuilder
*
* @author: wangchao
* @Date: 2025/10/21 11:02
* @since 7.0.0-RC2
**/
public class ServerUrlBuilder {
/**
* parse agent server protocol
*
* @return protocol
*/
public static String getAgentServerProtocol() {
String server = getAgentServerUrl();
return server.split("://")[0];
}
private static String getAgentServerUrl() {
return ConfigProvider.getConfig().getValue(AgentConstants.ConfigProperties.AGENT_SERVER, String.class);
}
/**
* get agent server base uri
*
* @return uri
*/
public static URI buildBaseUri() {
String server = getAgentServerUrl();
return URI.create(server);
}
/**
* get agent endpoint uri
*
* @param path interface path
* @return path uri
*/
public static URI buildEndpointUri(String path) {
String server = getAgentServerUrl();
return URI.create(String.format(Locale.getDefault(), "%s/%s", server, path));
}
}

View File

@ -0,0 +1,87 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.common;
import io.quarkus.arc.Unremovable;
import io.quarkus.runtime.ShutdownEvent;
import jakarta.annotation.PostConstruct;
import jakarta.enterprise.event.Observes;
import jakarta.inject.Singleton;
import lombok.Data;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
/**
* MutinyExecutor
*
* @author: wangchao
* @Date: 2025/5/21 11:14
* @since 7.0.0-RC2
**/
@Singleton
@Data
@Unremovable
public class MutinyExecutor {
@ConfigProperty(name = "agent.thread-pool.max-threads")
int poolSize;
private ExecutorService workerPool;
private ScheduledExecutorService scheduledExecutorService;
/**
* init
*/
@PostConstruct
void init() {
workerPool = Executors.newFixedThreadPool(poolSize);
scheduledExecutorService = Executors.newScheduledThreadPool(poolSize);
}
/**
* execute
*
* @param command command
*/
public void execute(Runnable command) {
workerPool.execute(command);
}
/**
* schedule
*
* @param command command
* @param delay delay
* @param unit unit
* @return ScheduledFuture
*/
public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
return scheduledExecutorService.scheduleWithFixedDelay(command, delay, delay, unit);
}
void onStop(@Observes ShutdownEvent event) {
if (workerPool != null) {
workerPool.shutdown();
}
if (scheduledExecutorService != null) {
scheduledExecutorService.shutdown();
}
}
}

View File

@ -0,0 +1,77 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.config;
import org.opengauss.agent.exception.AgentException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
/**
* AgentSSLContext
*
* @author: wangchao
* @Date: 2025/10/21 16:09
* @since 7.0.0-RC3
**/
public class AgentSslContext {
private static final TrustManager[] TRUST_MANAGERS = {new InsecureTrustManager()};
/**
* config and init ssl context
*
* @return ssl context
* @throws NoSuchAlgorithmException NoSuchAlgorithmException
* @throws KeyManagementException KeyManagementException
*/
public static SSLContext configureSslContext() throws NoSuchAlgorithmException, KeyManagementException {
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, TRUST_MANAGERS, new java.security.SecureRandom());
return sslContext;
}
/**
* get x509 trust manager
*
* @return X509TrustManager
*/
public static X509TrustManager getX509TrustManager() {
if (TRUST_MANAGERS != null && TRUST_MANAGERS[0] instanceof X509TrustManager x509TrustManager) {
return x509TrustManager;
}
throw new AgentException("init trust manager failed");
}
private static class InsecureTrustManager implements X509TrustManager {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}
}

View File

@ -0,0 +1,50 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.config;
import jakarta.inject.Singleton;
import lombok.Data;
import org.eclipse.microprofile.config.inject.ConfigProperty;
/**
* AppConfig
*
* @author: wangchao
* @Date: 2025/4/26 15:46
* @Description: AppConfig
* @since 7.0.0-RC2
**/
@Singleton
@Data
public class AppConfig {
@ConfigProperty(name = "agent.id")
Long agentId;
@ConfigProperty(name = "agent.name")
String appName;
@ConfigProperty(name = "agent.version")
String appVersion;
@ConfigProperty(name = "agent.server")
String appServerUrl;
@ConfigProperty(name = "agent.heartbeat.interval")
int heartbeatInterval;
@ConfigProperty(name = "agent.heartbeat.break-wait-max-times")
int heartbeatBreakWaitMaxTimes;
@ConfigProperty(name = "agent.os.command-write-list", defaultValue = "")
String osCommandWriteList;
String osCommandDefaultWriteList
= "date,dig,ping,ls,echo,dir,wc,head,java,git,cat,mkdir,tail,grep,touch,less,sort,id,javac";
}

View File

@ -0,0 +1,73 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.config;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import io.quarkus.runtime.StartupEvent;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
import jakarta.inject.Inject;
import lombok.extern.slf4j.Slf4j;
import org.opengauss.agent.utils.OsCommandUtils;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
/**
* AppConfigDistributor
*
* @author: wangchao
* @Date: 2025/7/7 15:28
* @since 7.0.0-RC2
**/
@Slf4j
@ApplicationScoped
public class AppConfigDistributor {
@Inject
AppConfig appConfig;
/**
* onStart initialize AppConfigDistributor instance when application start
*
* @param event StartupEvent
*/
void onStart(@Observes StartupEvent event) {
initializeOsCommandWriteList();
}
private void initializeOsCommandWriteList() {
String osCommandWriteList = appConfig.getOsCommandWriteList();
String osCommandDefaultWriteList = appConfig.getOsCommandDefaultWriteList();
List<String> writeList = new LinkedList<>();
if (StrUtil.isNotEmpty(osCommandDefaultWriteList)) {
writeList.addAll(Arrays.asList(osCommandDefaultWriteList.split(",")));
log.info("initialized osCommand default write list : {}", osCommandDefaultWriteList);
}
if (StrUtil.isNotEmpty(osCommandWriteList)) {
writeList.addAll(Arrays.asList(osCommandWriteList.split(",")));
log.info("initialized osCommand custom write list : {}", osCommandWriteList);
}
if (CollUtil.isNotEmpty(writeList)) {
OsCommandUtils.forceRefreshAllowedCommand(writeList);
log.info("initialized osCommand write list: {}", OsCommandUtils.getAllowedCommands());
} else {
log.warn("initialized osCommand write list empty");
}
}
}

View File

@ -0,0 +1,115 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.constant;
/**
* AgentConstants
*
* @author: wangchao
* @date: 2025/5/8 11:40
* @since 7.0.0-RC2
**/
public interface AgentConstants {
/**
* NULL string
*/
String NULL = "null";
/**
* heartbeat status up
*/
String HEARTBEAT_STATUS_UP = "UP";
/**
* heartbeat status down
*/
String HEARTBEAT_STATUS_DOWN = "DOWN";
/**
* deviation threshold 50%
*/
double DEVIATION_THRESHOLD = 0.5;
/**
* Duration Timing Constant
*/
interface Duration {
/**
* duration time split T
*/
String SPLIT_DATE_AND_TIME = "T";
/**
* duration time unit Y/M
*/
String YEAR = "Y";
/**
* duration time unit Y/M
*/
String MONTH = "M";
/**
* duration time prefix P
*/
String PREFIX_DATE = "P";
/**
* duration time prefix PT
*/
String PREFIX_ONLE_TIME = "PT";
}
/**
* MetricTranslate
*/
interface MetricTranslate {
/**
* metric name unknown
*/
String DEFAULT_NAME = "unknown_metric";
/**
* metric description, no description
*/
String DEFAULT_DESC = "no_description";
/**
* metric unit, no unit
*/
String DEFAULT_UNIT = "unitless";
/**
* metric type, no defined
*/
String DEFAULT_TYPE = "UNDEFINED";
}
/**
* agent config properties
*/
interface ConfigProperties {
/**
* https protocol
*/
String HTTPS = "https";
/**
* agnet server properties name
*/
String AGENT_SERVER = "agent.server";
}
}

View File

@ -0,0 +1,61 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.entity;
import lombok.Data;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
/**
* DataPoint
*
* @author: wangchao
* @Date: 2025/3/15 11:53
* @Description: DataPoint
* @since 7.0.0-RC2
**/
@Data
public class DataPoint {
private String value;
private Instant epochNanos;
private Instant startEpochNanos;
private Map<String, String> attributes = new HashMap<>();
@Override
public String toString() {
return "value=" + value;
}
/**
* used display data details
*
* @return string
*/
public String detail() {
return "{value='" + value + '\'' + ", startEpochNanos='" + startEpochNanos + '\'' + ", epochNanos='"
+ epochNanos + '\'' + ", attributes=" + formatAttributes() + "}";
}
private String formatAttributes() {
return attributes.entrySet()
.stream()
.map(entry -> entry.getKey() + "='" + entry.getValue() + "'")
.collect(Collectors.joining(", ", "{", "}"));
}
}

View File

@ -0,0 +1,47 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* HeartbeatReport
*
* @author: wangchao
* @Date: 2025/2/27 09:07
* @Description: HeartbeatReport
* @since 7.0.0-Rc2
**/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class HeartbeatHeader {
private String agentName;
private String instanceId;
private String agentAddress;
private String target;
/**
* Convert to heartbeat header string
*
* @return heartbeat header string
*/
public String toHeartbeatHeader() {
return agentName + ":" + agentAddress + " : " + instanceId + "->" + target;
}
}

View File

@ -0,0 +1,64 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.entity;
import static org.opengauss.agent.constant.AgentConstants.HEARTBEAT_STATUS_UP;
import com.cronutils.utils.StringUtils;
import lombok.Data;
import java.time.Instant;
/**
* HeartbeatReport
*
* @author: wangchao
* @Date: 2025/2/27 09:07
* @Description: HeartbeatReport
* @since 7.0.0-RC2
**/
@Data
public class HeartbeatReport {
private Instant timestamps;
private String status;
private Long agentId;
private String additionalInfo;
/**
* Constructor
*
* @param agentId agent id
* @param timestamps timestamps
*/
public HeartbeatReport(Long agentId, Instant timestamps) {
this.agentId = agentId;
this.timestamps = timestamps;
this.status = HEARTBEAT_STATUS_UP;
}
/**
* Convert to heartbeat report
*
* @return heartbeat report
*/
public String toHeartbeat() {
if (StringUtils.isEmpty(additionalInfo)) {
return "send heartbeat report " + status + ", at " + timestamps;
}
return "send heartbeat report " + status + ", at " + timestamps + ", additionalInfo: " + additionalInfo;
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.entity;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* HostBaseInfo
*
* @author: wangchao
* @Date: 2025/4/8 17:18
* @Description: HostBaseInfo
* @since 7.0.0-RC2
**/
@Data
@Builder
@Accessors(chain = true)
public class HostBaseInfo {
private String hostName;
private String cpuModel;
private String cpuArchitecture;
private long cpuFreq;
private int physicalCores;
private int logicalCores;
private String osName;
private String osVersion;
private String osBuild;
}

View File

@ -0,0 +1,49 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.entity;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* HostTaskDefinition
*
* @author: wangchao
* @Date: 2025/4/8 15:44
* @Description: HostTaskDefinition
* @since 7.0.0-RC2
**/
@Data
@EqualsAndHashCode(callSuper = true)
public class HostTaskDefinition extends TaskDefinition {
private boolean isLocal;
/**
* HostTaskDefinition
*
* @param isLocal isLocal
*/
public HostTaskDefinition(boolean isLocal) {
this.isLocal = isLocal;
}
/**
* HostTaskDefinition
*/
public HostTaskDefinition() {
this(true);
}
}

View File

@ -0,0 +1,66 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.entity;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.List;
import java.util.stream.Collectors;
/**
* Metric
*
* @author: wangchao
* @Date: 2025/3/15 11:51
* @Description: Metric
* @since 7.0.0-RC2
**/
@Getter
@NoArgsConstructor
public class Metric {
private String name;
private String description;
private String unit;
private String type;
@Setter
private List<DataPoint> points;
/**
* constructor
*
* @param name metric name
* @param description metric description
* @param unit metric unit
* @param type metric type
*/
public Metric(String name, String description, String unit, String type) {
this.name = name;
this.description = description;
this.unit = unit;
this.type = type;
}
@Override
public String toString() {
return "\n|---------- Metric ----------\n| Name: " + name + "\n| Description: " + description + "\n| Unit: "
+ unit + "\n| Type: " + type + "\n|------ Data Points ------" + (points == null || points.isEmpty()
? "\n|(No data points)"
: points.stream().map(dp -> "\n| " + dp.toString().replace("\n", "\n| ")).collect(Collectors.joining()))
+ "\n|--------------------";
}
}

View File

@ -0,0 +1,37 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.entity;
import lombok.Data;
import java.util.List;
/**
* MetricRequest
*
* @author: wangchao
* @Date: 2025/3/21 09:14
* @Description: MetricRequest
* @since 7.0.0-RC2
**/
@Data
public class MetricRequest {
private String agentId;
private String instanceId;
private String timestamp;
private String hostIp;
private List<Metric> metrics;
}

View File

@ -0,0 +1,34 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.entity;
/**
* OsCmdResult
*
* @author: wangchao
* @Date: 2025/7/5 11:04
* @since 7.0.0-RC2
**/
public record OsCmdResult(int exitCode, String output) {
/**
* is success
*
* @return true if exitCode is 0, false otherwise
*/
public boolean isSuccess() {
return exitCode == 0;
}
}

View File

@ -0,0 +1,60 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.entity;
import cn.hutool.json.JSONUtil;
import lombok.Data;
/**
* ProcessedData
*
* @author: wangchao
* @Date: 2025/2/28 11:50
* @Description: ProcessedData
* @since 7.0.0-RC2
**/
@Data
public class ProcessedData {
String message;
/**
* Constructor
*
* @param raw RawData
*/
public ProcessedData(RawData raw) {
this.message = raw.getMessage();
}
/**
* toJson
*
* @return String
*/
public String toJson() {
return JSONUtil.toJsonStr(this);
}
/**
* fromJson
*
* @param json String
* @return ProcessedData
*/
public static ProcessedData fromJson(String json) {
return JSONUtil.toBean(json, ProcessedData.class);
}
}

View File

@ -0,0 +1,33 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* RawData
*
* @author: wangchao
* @Date: 2025/2/28 11:50
* @Description: RawData
* @since 7.0.0-RC2
**/
@Data
@AllArgsConstructor
public class RawData {
String message;
}

View File

@ -0,0 +1,44 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.entity;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* RemoteHostTaskDefinition
*
* @author: wangchao
* @Date: 2025/4/8 15:44
* @Description: RemoteHostTaskDefinition
* @since 7.0.0-RC2
**/
@Data
@EqualsAndHashCode(callSuper = true)
public class RemoteHostTaskDefinition extends HostTaskDefinition {
private String hostName;
private String hostIp;
private String hostPort;
private String hostUser;
private String hostPassword;
/**
* RemoteHostTaskDefinition
*/
public RemoteHostTaskDefinition() {
super(false);
}
}

View File

@ -0,0 +1,57 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.entity;
import lombok.Data;
import org.opengauss.agent.entity.task.AgentClusterVo;
import org.opengauss.agent.entity.task.TaskMetricsDefinitionVo;
import org.opengauss.agent.enums.ObjectType;
import org.opengauss.agent.enums.StoragePolicy;
import org.opengauss.agent.enums.TaskType;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* TaskDefinition
*
* @author: wangchao
* @Date: 2025/4/8 15:44
* @Description: TaskDefinition
* @since 7.0.0-RC2
**/
@Data
public class TaskDefinition {
private Long taskId;
private String taskName;
private String agentId;
private TaskType taskType;
private String groupTag;
private String pluginsTag;
private ObjectType operateObjType;
private String operateObj;
private String receiveApi;
private long period;
private long estimatedExecutionTime;
private TimeUnit unit;
private StoragePolicy storagePolicy;
private String dataSendTarget;
private boolean isDbTemplate;
private AgentClusterVo clusterConfig;
private List<String> collectorList;
private List<TaskMetricsDefinitionVo> metricsDefinitionList;
}

View File

@ -0,0 +1,74 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.entity;
import org.opengauss.agent.entity.task.AgentTaskVo;
import org.opengauss.agent.entity.task.TaskTemplateDefinitionVo;
import org.opengauss.agent.enums.ObjectType;
import org.opengauss.agent.utils.DurationUtils;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
* TaskDefinitionBuilder
*
* @author: wangchao
* @Date: 2025/4/28 18:34
* @Description: TaskDefinitionBuilder
* @since 7.0.0-RC2
**/
public class TaskDefinitionBuilder {
/**
* builder
*
* @param taskConfig taskConfig
* @return TaskDefinition
*/
public static TaskDefinition builder(AgentTaskVo taskConfig) {
TaskDefinition taskDefinition = new TaskDefinition();
taskDefinition.setTaskId(taskConfig.getTaskId());
taskDefinition.setTaskName(taskConfig.getTaskName());
taskDefinition.setAgentId(taskConfig.getAgentId());
TaskTemplateDefinitionVo definition = taskConfig.getTemplateDefinition();
taskDefinition.setTaskType(definition.getType());
taskDefinition.setStoragePolicy(definition.getStoragePolicy());
taskDefinition.setGroupTag(definition.getGroupTag());
taskDefinition.setPluginsTag(definition.getPluginsTag());
taskDefinition.setOperateObjType(definition.getOperateObjType());
taskDefinition.setOperateObj(definition.getOperateObj());
taskDefinition.setReceiveApi(definition.getReceiveApi());
taskDefinition.setPeriod(DurationUtils.parseToMillis(definition.getPeriod()));
taskDefinition.setEstimatedExecutionTime(Math.max(definition.getEstimatedExecutionTime(), 5));
taskDefinition.setUnit(TimeUnit.MILLISECONDS);
taskDefinition.setDataSendTarget(definition.getReceiveApi());
taskDefinition.setDbTemplate(isDbTemplate(definition));
taskDefinition.setClusterConfig(taskConfig.getClusterConfig());
taskDefinition.setMetricsDefinitionList(taskConfig.getMetricsDefinitionList());
taskDefinition.setCollectorList(taskConfig.getCollectorMetricDetails());
return taskDefinition;
}
/**
* isDbTemplate
*
* @param definition definition
* @return boolean
*/
public static boolean isDbTemplate(TaskTemplateDefinitionVo definition) {
return Objects.equals(definition.getOperateObjType(), ObjectType.DB);
}
}

View File

@ -0,0 +1,70 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.entity;
import cn.hutool.core.util.IdUtil;
import lombok.Data;
import org.opengauss.agent.enums.TaskType;
import org.opengauss.agent.exception.AgentException;
import org.opengauss.agent.service.task.TaskExecutor;
import java.time.Instant;
import java.util.Optional;
/**
* TaskExecution
*
* @author: wangchao
* @Date: 2025/4/9 09:28
* @Description: TaskExecution
* @since 7.0.0-RC2
**/
@Data
public class TaskExecution {
private String executionId;
private Long taskId;
private TaskDefinition taskDefinition;
private TaskExecutor executor;
private Instant startTime;
private Instant endTime;
private String status;
private String errorMessage;
/**
* Constructor for TaskExecution
*
* @param taskDefinition taskDefinition
*/
public TaskExecution(TaskDefinition taskDefinition) {
this.executionId = IdUtil.getSnowflakeNextIdStr();
this.taskId = taskDefinition.getTaskId();
this.taskDefinition = taskDefinition;
this.startTime = Instant.now();
this.status = "running";
}
/**
* getTaskType
*
* @return TaskType
*/
public TaskType getTaskType() {
return Optional.ofNullable(taskDefinition)
.orElseThrow(() -> new AgentException("TaskExecution [" + executionId + "] taskDefinition is null"))
.getTaskType();
}
}

View File

@ -0,0 +1,39 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.entity.task;
import lombok.Data;
import lombok.ToString;
/**
* AgentClusterVo
*
* @author: wangchao
* @date: 2025/4/18 11:40
* @since 7.0.0-RC2
**/
@Data
public class AgentClusterVo {
private String clusterId;
private String clusterNodeId;
private String dataBaseType;
private String hostIp;
private String port;
private String username;
@ToString.Exclude
private String dbPassword;
private String url;
}

View File

@ -0,0 +1,39 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.entity.task;
import lombok.Data;
import java.util.List;
/**
* AgentTaskVo
*
* @author: wangchao
* @Date: 2025/4/21 16:34
* @Description: AgentTaskVo
* @since 7.0.0-RC2
**/
@Data
public class AgentTaskVo {
private Long taskId;
private String taskName;
private String agentId;
private TaskTemplateDefinitionVo templateDefinition;
private List<String> collectorMetricDetails;
private List<TaskMetricsDefinitionVo> metricsDefinitionList;
private AgentClusterVo clusterConfig;
}

View File

@ -0,0 +1,39 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.entity.task;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* AgentTaskMetricsDefinition
*
* @author: wangchao
* @Date: 2025/4/16 09:34
* @Description: AgentTaskMetricsDefinition
* @since 7.0.0-RC2
**/
@Data
@EqualsAndHashCode
public class TaskMetricsDefinitionVo {
private String name;
private String description;
private String fieldName;
private String unit;
private String dataType;
private String prop;
private String collectCmd;
}

View File

@ -0,0 +1,77 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.entity.task;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.opengauss.agent.enums.ObjectType;
import org.opengauss.agent.enums.StoragePolicy;
import org.opengauss.agent.enums.TaskType;
/**
* TaskTemplateDefinitionVo
*
* @author: wangchao
* @Date: 2025/4/16 09:34
* @Description: TaskTemplateDefinitionVo
* @since 7.0.0-RC2
**/
@Data
@EqualsAndHashCode
public class TaskTemplateDefinitionVo {
private String name;
private TaskType type;
private String groupTag;
private String pluginsTag;
private ObjectType operateObjType;
private String operateObj;
/**
* <pre>
* Obtains a Duration from a text string such as PnDTnHnMn.nS.
* time period and duration ,1year 2month 3week 4day 5hour 6minutes 7.5second eg. "P1Y2M3W4DT5H6M7.5S"
*
* The formats accepted are based on the ISO-8601 duration format PnDTnHnMn.nS with days considered to be exactly
* 24 hours.
* For example, P1Y2M3W4DT5H6M7S is a valid duration.
* A date-based amount of time in the ISO-8601 calendar system, such as '2 years, 3 months and 4 days'.
* </pre>
*/
private String period;
private String collectMetric;
private StoragePolicy storagePolicy;
/**
* <pre>
* Obtains a Duration from a text string such as PnDTnHnMn.nS.
* time period and duration,1year 2month 3week 4day 5hour 6minutes 7.5second eg.
* time period and duration,1year 2month 3week 4day 5hour 6minutes 7.5second eg. "P1Y2M3W4DT5H6M7.5S"
*
* The formats accepted are based on the ISO-8601 duration format PnDTnHnMn.nS with days considered to be exactly
* 24 hours.
* For example, * For example, P1Y2M3W4DT5H6M7S is a valid duration.
* A date-based amount of time in the ISO-8601 calendar system, such as '2 years, 3 months and 4 days'.
* </pre>
*/
private String keepPeriod;
/**
* task estimated execution time , unit is milliseconds
*/
private long estimatedExecutionTime;
private String receiveApi;
}

View File

@ -0,0 +1,52 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.enums;
import com.zaxxer.hikari.HikariConfig;
import java.sql.Connection;
import java.sql.SQLException;
/**
* ConnectionContainer
*
* @author: wangchao
* @Date: 2025/5/17 09:22
* @Description: ConnectionContainer
* @since 7.0.0-RC2
**/
public interface ConnectionContainer {
/**
* configureDataSource
*
* @param hikariConfig hikariConfig
* @param url url
* @param user user
* @param password password
*/
void configureDataSource(HikariConfig hikariConfig, String url, String user, String password);
/**
* getConnection
*
* @param url url
* @param user user
* @param password password
* @return Connection
* @throws SQLException SQLException
*/
Connection getConnection(String url, String user, String password) throws SQLException;
}

View File

@ -0,0 +1,179 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.enums;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import lombok.Getter;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Locale;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.sql.DataSource;
/**
* DatabaseType
*
* @author: wangchao
* @Date: 2025/5/6 17:31
* @Description: DatabaseType
* @since 7.0.0-RC2
**/
@Getter
public enum DatabaseType implements ConnectionContainer {
MYSQL("mysql", "com.mysql.cj.jdbc.Driver") {
@Override
public void configureDataSource(HikariConfig config, String url, String user, String password) {
config.setJdbcUrl(url);
config.setUsername(user);
config.setPassword(password);
config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("prepStmtCacheSize", "250");
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
}
},
OPENGAUSS("opengauss", "org.opengauss.Driver") {
@Override
public void configureDataSource(HikariConfig config, String url, String user, String password) {
config.setJdbcUrl(url);
config.setUsername(user);
config.setPassword(password);
config.addDataSourceProperty("ssl", "false");
}
},
POSTGRESQL("postgresql", "org.postgresql.Driver") {
@Override
public void configureDataSource(HikariConfig config, String url, String user, String password) {
config.setJdbcUrl(url);
config.setUsername(user);
config.setPassword(password);
}
},
ORACLE("oracle", "oracle.jdbc.driver.OracleDriver") {
@Override
public void configureDataSource(HikariConfig config, String url, String user, String password) {
config.setJdbcUrl(url);
config.setUsername(user);
config.setPassword(password);
config.addDataSourceProperty("implicitCachingEnabled", "true");
}
},
SQL_SERVER("sqlserver", "com.microsoft.sqlserver.jdbc.SQLServerDriver") {
@Override
public void configureDataSource(HikariConfig config, String url, String user, String password) {
config.setJdbcUrl(url);
config.setUsername(user);
config.setPassword(password);
config.addDataSourceProperty("encrypt", "false");
}
},
UNKNOWN("", null) {
@Override
public void configureDataSource(HikariConfig config, String url, String user, String password) {
throw new UnsupportedOperationException("Unknown database type");
}
@Override
public Connection getConnection(String url, String user, String password) throws SQLException {
throw new UnsupportedOperationException();
}
};
private final String value;
private final String driverClass;
private final ConcurrentMap<String, DataSource> dataSources = new ConcurrentHashMap<>();
DatabaseType(String value, String driverClass) {
this.value = value.trim().toLowerCase(Locale.ENGLISH);
this.driverClass = driverClass;
}
@Override
public Connection getConnection(String url, String user, String password) throws SQLException {
if (this == UNKNOWN) {
throw new SQLException("Unsupported database type");
}
String poolKey = generatePoolKey(url, user);
DataSource dataSource = dataSources.computeIfAbsent(poolKey, k -> {
HikariConfig hikariConfig = createHikariConfig(url, user, password);
return new HikariDataSource(hikariConfig);
});
return dataSource.getConnection();
}
private HikariConfig createHikariConfig(String url, String user, String password) {
HikariConfig config = new HikariConfig();
config.setDriverClassName(driverClass);
config.setPoolName("hikariPool-" + this.value + "_" + url.hashCode());
config.setMaximumPoolSize(10);
config.setMinimumIdle(2);
config.setConnectionTimeout(30000);
config.setIdleTimeout(600000);
config.setMaxLifetime(1800000);
configureDataSource(config, url, user, password);
return config;
}
private String generatePoolKey(String url, String user) {
return url + "|" + user;
}
/**
* getByValue
*
* @param value String
* @return Matching DatabaseType instance
*/
public static DatabaseType getByValue(String value) {
String normalizedValue = value == null ? "" : value.trim().toLowerCase(Locale.ENGLISH);
for (DatabaseType type : values()) {
if (type.value.equals(normalizedValue)) {
return type;
}
}
return UNKNOWN;
}
/**
* current database type is support
*
* @param value String
* @return Boolean
*/
public static boolean isSupport(String value) {
return getByValue(value) != UNKNOWN;
}
/**
* shutdownAll
* Shutdown all data sources in all database types.
*/
public static void shutdownAll() {
for (DatabaseType type : values()) {
type.dataSources.values().forEach(ds -> {
if (ds instanceof HikariDataSource) {
((HikariDataSource) ds).close();
}
});
type.dataSources.clear();
}
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* MetricDataType
*
* @author: wangchao
* @Date: 2025/5/17 11:39
* @since 7.0.0-RC2
**/
@Getter
@AllArgsConstructor
public enum MetricDataType {
STRING("String"),
LONG("Long"),
DOUBLE("Double"),
BOOLEAN("Boolean");
private final String value;
}

View File

@ -0,0 +1,36 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* ObjectType
*
* @author: wangchao
* @Date: 2025/5/21 11:14
* @since 7.0.0-RC2
**/
@Getter
@AllArgsConstructor
public enum ObjectType {
OSHI("oshi"),
OS("os"),
DB("db"),
HTTP("http");
private final String value;
}

View File

@ -0,0 +1,39 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* StoragePolicy
*
* @author: wangchao
* @Date: 2025/4/7 10:47
* @Description: StoragePolicy
* @since 7.0.0-RC2
**/
@Getter
@AllArgsConstructor
public enum StoragePolicy {
CUSTOM("custom"),
REAL_TIME("real_time"),
HISTORY("history"),
FINGERPRINT("fingerprint"),
TREE("tree");
private final String value;
}

View File

@ -0,0 +1,55 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.List;
/**
* TaskType
*
* @author: wangchao
* @Date: 2025/4/8 16:40
* @Description: TaskType
* @since 7.0.0-RC2
**/
@Getter
@AllArgsConstructor
public enum TaskType {
OSHI_FIXED_METRIC("OSHI_FIXED_METRIC"),
OSHI_DYNAMIC_METRIC("OSHI_DYNAMIC_METRIC"),
OS_METRIC("OS_METRIC"),
DB_METRIC("DB_METRIC"),
OS_PIPE("OS_PIPE"),
DB_PIPE("DB_PIPE");
private String value;
private static final List<TaskType> METRIC_TASK_TYPES = List.of(OSHI_FIXED_METRIC, OSHI_DYNAMIC_METRIC, OS_METRIC,
DB_METRIC);
/**
* isOtelMetricTask
*
* @param taskType taskType
* @return boolean
*/
public static boolean isOtelMetricTask(TaskType taskType) {
return METRIC_TASK_TYPES.contains(taskType);
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.exception;
/**
* AgentException
*
* @author: wangchao
* @date: 2025/5/8 11:40
* @since 7.0.0-RC2
**/
public class AgentException extends RuntimeException {
/**
* agent exception constructor
*
* @param message message
*/
public AgentException(String message) {
super(message);
}
/**
* agent exception constructor
*
* @param message message
* @param cause cause
*/
public AgentException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.exception;
/**
* TaskExecutionException
*
* @author: wangchao
* @Date: 2025/4/9 09:32
* @Description: TaskExecutionException
* @since 7.0.0-RC2
**/
public class TaskExecutionException extends AgentException {
/**
* agent task execution exception constructor
*
* @param message message
*/
public TaskExecutionException(String message) {
super(message);
}
}

View File

@ -0,0 +1,173 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.resource;
import io.quarkus.arc.Unremovable;
import io.quarkus.runtime.ShutdownEvent;
import jakarta.enterprise.event.Observes;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import lombok.extern.slf4j.Slf4j;
import retrofit2.http.Body;
import org.opengauss.agent.client.AgentServerClient;
import org.opengauss.agent.client.ServerClientFactory;
import org.opengauss.agent.common.MutinyExecutor;
import org.opengauss.agent.config.AppConfig;
import org.opengauss.agent.entity.TaskExecution;
import org.opengauss.agent.entity.task.AgentTaskVo;
import org.opengauss.agent.exception.AgentException;
import org.opengauss.agent.service.task.TaskManager;
import org.opengauss.agent.service.task.core.TaskExecutionRecordService;
import org.opengauss.agent.utils.RsaUtils;
import java.time.Instant;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* TaskResource
*
* @author: wangchao
* @Date: 2025/2/28 20:04
* @Description: TaskResource
* @since 7.0.0-RC2
**/
@Unremovable
@Slf4j
@Path("/agent")
public class TaskResource {
@Inject
MutinyExecutor mutinyExecutor;
@Inject
TaskManager taskManager;
@Inject
AppConfig appConfig;
@Inject
TaskExecutionRecordService taskExecutionRecordService;
AgentServerClient agentServerClient;
@Inject
ServerClientFactory clientFactory;
private final AtomicBoolean isCalledTask = new AtomicBoolean(false);
private final Object taskLock = new Object();
private ScheduledFuture<?> scheduledTask;
/**
* init
*/
void init() {
agentServerClient = clientFactory.createAgentServerClient();
scheduledTask = mutinyExecutor.schedule(() -> {
if (taskManager.isEmpty() || !isCalledTask.get()) {
log.info("no task to inspect");
try {
agentServerClient.taskCallbackStart(appConfig.getAgentId());
log.info("task callback start success");
isCalledTask.set(true);
} catch (AgentException ex) {
log.error("task callback start failed", ex);
}
} else {
cancelSchedule();
}
}, 5, TimeUnit.SECONDS);
log.info("TaskResource init start task callback scheduler success");
}
private void cancelSchedule() {
synchronized (taskLock) {
if (scheduledTask != null && !scheduledTask.isDone()) {
scheduledTask.cancel(true);
}
}
}
/**
* stop
*
* @param event event
*/
void onStop(@Observes ShutdownEvent event) {
cancelSchedule();
}
/**
* health check: other processes can call this endpoint to check if the process is still alive
* /agent/health
*
* @return string
*/
@GET
@Path("/health")
@Produces(MediaType.APPLICATION_JSON)
public String health() {
log.debug("agent health {}", Instant.now());
return "success";
}
/**
* get secret rsa pub key
*
* @return pub key
*/
@GET
@Path("/pubKey")
@Produces(MediaType.APPLICATION_JSON)
public String pubKey() {
return RsaUtils.publicKey();
}
/**
* start task
*
* @param taskConfig task config
*/
@POST
@Path("/task/start")
@Produces(MediaType.APPLICATION_JSON)
public void start(@Body AgentTaskVo taskConfig) {
log.info("start task {}", taskConfig);
isCalledTask.set(true);
mutinyExecutor.getWorkerPool().execute(() -> {
TaskExecution execution = taskManager.startTask(taskConfig);
if (Objects.nonNull(execution)) {
taskExecutionRecordService.save(execution);
}
});
}
/**
* stop task
*
* @param taskId task id
*/
@POST
@Path("/task/stop")
@Produces(MediaType.APPLICATION_JSON)
public void stop(Long taskId) {
Optional<TaskExecution> execution = taskManager.stop(taskId);
execution.ifPresent(taskExecution -> {
taskExecutionRecordService.save(taskExecution);
});
}
}

View File

@ -0,0 +1,119 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.service.heartbeat;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.net.NetUtil;
import io.quarkus.runtime.Quarkus;
import io.quarkus.runtime.ShutdownEvent;
import io.quarkus.runtime.StartupEvent;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
import jakarta.inject.Inject;
import lombok.extern.slf4j.Slf4j;
import org.opengauss.agent.client.HeartbeatServerClient;
import org.opengauss.agent.client.ServerClientFactory;
import org.opengauss.agent.config.AppConfig;
import org.opengauss.agent.constant.AgentConstants;
import org.opengauss.agent.entity.HeartbeatHeader;
import org.opengauss.agent.entity.HeartbeatReport;
import java.time.Instant;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* HeartbeatScheduler
*
* @author: wangchao
* @Date: 2025/2/26 18:45
* @Description: HeartbeatScheduler
* @since 7.0.0-RC2
**/
@Slf4j
@ApplicationScoped
public class HeartbeatScheduler {
private static final String INSTANCE_ID = UUID.randomUUID().toString();
private static final AtomicInteger HEARTBEAT_BREAK_TIMES = new AtomicInteger(0);
HeartbeatHeader heartbeatHeader;
ScheduledExecutorService scheduler;
@Inject
AppConfig appConfig;
@Inject
ServerClientFactory clientFactory;
HeartbeatServerClient heartbeatServerClient = null;
/**
* onStart
*
* @param event startup event
*/
void onStart(@Observes StartupEvent event) {
log.info("agent config : {}", appConfig);
initHeartbeatHeader();
scheduler = Executors.newSingleThreadScheduledExecutor();
heartbeatServerClient = clientFactory.createHeartbeatClient();
scheduler.scheduleAtFixedRate(this::sendHeartbeat, 1, appConfig.getHeartbeatInterval(), TimeUnit.SECONDS);
}
/**
* stop ,release resources
*
* @param event shutdown event
*/
void onStop(@Observes ShutdownEvent event) {
if (scheduler != null) {
scheduler.shutdownNow();
}
HeartbeatReport report = new HeartbeatReport(appConfig.getAgentId(), Instant.now());
try {
report.setStatus(AgentConstants.HEARTBEAT_STATUS_DOWN);
heartbeatServerClient.deregister(heartbeatHeader.toHeartbeatHeader(), report);
log.info("server deregister");
} catch (Exception ex) {
log.warn("Failed to deregister server:" + report);
}
}
private void initHeartbeatHeader() {
heartbeatHeader = new HeartbeatHeader(appConfig.getAppName(), INSTANCE_ID, NetUtil.getLocalhostStr(),
appConfig.getAppServerUrl());
}
private void sendHeartbeat() {
HeartbeatReport report = new HeartbeatReport(appConfig.getAgentId(), Instant.now());
try {
heartbeatServerClient.heartbeat(heartbeatHeader.toHeartbeatHeader(), report);
HEARTBEAT_BREAK_TIMES.set(0);
log.info("agent heartbeat name=[{}] local=[{}] server=[{}]", heartbeatHeader.getAgentName(),
heartbeatHeader.getAgentAddress(), heartbeatHeader.getTarget());
} catch (Exception e) {
if (HEARTBEAT_BREAK_TIMES.get() <= appConfig.getHeartbeatBreakWaitMaxTimes()) {
HEARTBEAT_BREAK_TIMES.incrementAndGet();
log.error("Failed to send heartbeat name=[{}] local=[{}] server=[{}] {}",
heartbeatHeader.getAgentName(), heartbeatHeader.getAgentAddress(), heartbeatHeader.getTarget(),
e.getMessage());
} else {
log.error("Heartbeat detection failure count exceeds limit, process will exit soon");
Quarkus.asyncExit(1); // 优雅关闭
}
}
}
}

View File

@ -0,0 +1,130 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.service.otel;
import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.metrics.InstrumentType;
import io.opentelemetry.sdk.metrics.data.AggregationTemporality;
import io.opentelemetry.sdk.metrics.data.MetricData;
import io.opentelemetry.sdk.metrics.export.MetricExporter;
import lombok.extern.slf4j.Slf4j;
import retrofit2.Response;
import org.opengauss.agent.client.DynamicHttpClientBuilder;
import org.opengauss.agent.client.MetricHttpClient;
import org.opengauss.agent.entity.Metric;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* HttpMetricExporter
*
* @author: wangchao
* @Date: 2025/3/14 17:13
* @Description: HttpMetricExporter
* @since 7.0.0-RC2
**/
@Slf4j
public class HttpMetricExporter implements MetricExporter {
private final MetricHttpClient metricHttpClient;
private final String baseUrl;
private final AtomicBoolean isShutdown;
private final AggregationTemporality aggregationTemporality;
private final MetricDataTranslate metricDataTranslate = new MetricDataTranslate();
private final Map<String, Object> commonParams = new ConcurrentHashMap<>();
/**
* constructor
*
* @param baseUrl URL Template
* @param aggregationTemporality otel aggregation temporality
* @param commonParams common params
*/
public HttpMetricExporter(String baseUrl, AggregationTemporality aggregationTemporality,
Map<String, Object> commonParams) {
this.baseUrl = baseUrl;
this.aggregationTemporality = aggregationTemporality;
this.commonParams.putAll(commonParams);
this.isShutdown = new AtomicBoolean(false);
this.metricHttpClient = DynamicHttpClientBuilder.createHttpClient(baseUrl);
}
/**
* create http metric exporter with params
*
* @param baseUrl URL Template
* @param commonParams common params
* @return exporter
*/
public static HttpMetricExporter createWithParams(String baseUrl, Map<String, Object> commonParams) {
return new HttpMetricExporter(baseUrl, AggregationTemporality.CUMULATIVE, commonParams);
}
@Override
public CompletableResultCode export(Collection<MetricData> metrics) {
try {
String path = resolvePath(baseUrl, commonParams);
List<Metric> payload = metricDataTranslate.doTranslate(metrics);
Object objAgentId = commonParams.get("agentId");
Long agentId = objAgentId instanceof Long ? (Long) objAgentId : Long.valueOf(objAgentId.toString());
retrofit2.Call<Void> call = metricHttpClient.sendMetrics(path, (List<Long>) commonParams.get("taskIds"),
agentId, payload);
log.debug("Export http info | {}: {}", baseUrl, commonParams);
Response<Void> response = call.execute(); // 同步执行或使用enqueue异步
if (!response.isSuccessful()) {
log.error("Export failed | Code: {}", response.code());
return CompletableResultCode.ofFailure();
}
return CompletableResultCode.ofSuccess();
} catch (Exception e) {
log.error("Export failed", e);
return CompletableResultCode.ofFailure();
}
}
private String resolvePath(String urlTemplate, Map<String, Object> params) {
String path = urlTemplate;
for (Map.Entry<String, Object> entry : params.entrySet()) {
String placeholder = "{" + entry.getKey() + "}";
if (path.contains(placeholder)) {
path = path.replace(placeholder, entry.getValue().toString());
}
}
return path;
}
@Override
public CompletableResultCode flush() {
return CompletableResultCode.ofSuccess();
}
@Override
public CompletableResultCode shutdown() {
if (isShutdown.getAndSet(true)) {
return CompletableResultCode.ofSuccess();
}
return CompletableResultCode.ofSuccess();
}
@Override
public AggregationTemporality getAggregationTemporality(InstrumentType instrumentType) {
return aggregationTemporality;
}
}

View File

@ -0,0 +1,164 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.service.otel;
import static java.util.stream.Collectors.toList;
import static org.opengauss.agent.constant.AgentConstants.MetricTranslate.DEFAULT_DESC;
import static org.opengauss.agent.constant.AgentConstants.MetricTranslate.DEFAULT_NAME;
import static org.opengauss.agent.constant.AgentConstants.MetricTranslate.DEFAULT_TYPE;
import static org.opengauss.agent.constant.AgentConstants.MetricTranslate.DEFAULT_UNIT;
import io.opentelemetry.sdk.metrics.data.Data;
import io.opentelemetry.sdk.metrics.data.DoublePointData;
import io.opentelemetry.sdk.metrics.data.HistogramPointData;
import io.opentelemetry.sdk.metrics.data.LongPointData;
import io.opentelemetry.sdk.metrics.data.MetricData;
import io.opentelemetry.sdk.metrics.data.PointData;
import io.opentelemetry.sdk.metrics.data.SummaryPointData;
import lombok.extern.slf4j.Slf4j;
import org.opengauss.agent.constant.AgentConstants;
import org.opengauss.agent.entity.DataPoint;
import org.opengauss.agent.entity.Metric;
import org.opengauss.agent.exception.AgentException;
import java.time.Instant;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* MetricDataTranslate
*
* @author: wangchao
* @Date: 2025/3/15 14:14
* @Description: MetricDataTranslate
* @since 7.0.0-RC2
**/
@Slf4j
public class MetricDataTranslate {
/**
* doTranslate
*
* @param metrics metrics
* @return List<Metric>
*/
public List<Metric> doTranslate(Collection<MetricData> metrics) {
if (metrics == null || metrics.isEmpty()) {
log.warn("Input metrics collection is empty");
return Collections.emptyList();
}
return metrics.stream()
.filter(Objects::nonNull)
.map(this::convertMetric)
.collect(toList());
}
private Metric convertMetric(MetricData metric) {
Metric converted = new Metric(getName(metric), getDescription(metric), getUnit(metric), getType(metric));
converted.setPoints(getMetricDataPoint(metric));
log.debug("Converted metric: {} {}", converted, metric.getData());
return converted;
}
private List<DataPoint> getMetricDataPoint(MetricData metric) {
return Optional.ofNullable(metric.getData())
.map(Data::getPoints)
.orElse(Collections.emptyList())
.stream()
.map(this::convertDataPoint)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
private DataPoint convertDataPoint(PointData point) {
DataPoint dp;
try {
dp = new DataPoint();
dp.setValue(getPointValue(point)); // 保留原始数值类型
dp.setEpochNanos(translateEpochNanos(point.getEpochNanos()));
dp.setStartEpochNanos(translateEpochNanos(point.getStartEpochNanos()));
convertAttributes(point, dp);
} catch (AgentException ex) {
log.error("DataPoint conversion error: {}", ex.getMessage());
dp = null;
}
return dp;
}
private void convertAttributes(PointData point, DataPoint dp) {
point.getAttributes()
.forEach((key, value) -> dp.getAttributes().put(attributeConvert(key), attributeConvert(value)));
}
private String attributeConvert(Object obj) {
if (obj == null) {
return AgentConstants.NULL;
}
try {
if (obj instanceof Collection<?> collection) {
return collection.stream().map(this::attributeConvert).collect(Collectors.joining(","));
} else if (obj.getClass().isArray()) {
return Arrays.stream((Object[]) obj).map(this::attributeConvert).collect(Collectors.joining(","));
} else {
return obj.toString();
}
} catch (Exception e) {
log.error("Attribute conversion error: {}", e.getMessage());
throw new AgentException("Attribute conversion error " + e.getMessage());
}
}
private static Instant translateEpochNanos(Long epochNanos) {
long seconds = epochNanos / 1_000_000_000L; // 提取秒部分
int nanoAdjustment = (int) (epochNanos % 1_000_000_000L); // 剩余纳秒
return Instant.ofEpochSecond(seconds, nanoAdjustment);
}
private String getName(MetricData metric) {
return Optional.ofNullable(metric.getName()).orElse(DEFAULT_NAME);
}
private String getDescription(MetricData metric) {
return Optional.ofNullable(metric.getDescription()).orElse(DEFAULT_DESC);
}
private String getUnit(MetricData metric) {
return Optional.ofNullable(metric.getUnit()).orElse(DEFAULT_UNIT);
}
private String getType(MetricData metric) {
return Optional.ofNullable(metric.getType()).map(Enum::name).orElse(DEFAULT_TYPE);
}
private String getPointValue(PointData point) {
if (point instanceof LongPointData longPointData) {
return String.valueOf(longPointData.getValue());
} else if (point instanceof DoublePointData doublePointData) {
return String.valueOf(doublePointData.getValue());
} else if (point instanceof HistogramPointData histogramPointData) {
return String.valueOf(histogramPointData.getSum());
} else if (point instanceof SummaryPointData summaryPointData) {
return String.valueOf(summaryPointData.getSum());
} else {
throw new AgentException("Unsupported point data type");
}
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.service.task;
import org.opengauss.agent.entity.TaskDefinition;
import org.opengauss.agent.exception.TaskExecutionException;
/**
* TaskExecutor
*
* @author: wangchao
* @Date: 2025/4/9 09:32
* @Description: TaskExecutor
* @since 7.0.0-RC2
**/
public interface TaskExecutor {
/**
* initialize
*
* @param taskDefinition taskDefinition
* @throws TaskExecutionException TaskExecutionException
*/
void initialize(TaskDefinition taskDefinition) throws TaskExecutionException;
/**
* execute
*
* @throws TaskExecutionException TaskExecutionException
*/
void execute() throws TaskExecutionException;
/**
* cancel
*
* @param taskId taskId
*/
void cancel(Long taskId);
/**
* hasTaskRunning
*
* @return boolean
*/
boolean hasTaskRunning();
}

View File

@ -0,0 +1,88 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.service.task;
import io.quarkus.arc.Unremovable;
import jakarta.annotation.PostConstruct;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.opengauss.agent.enums.TaskType;
import org.opengauss.agent.exception.TaskExecutionException;
import org.opengauss.agent.service.task.core.DbMetricExecutor;
import org.opengauss.agent.service.task.core.DbPipeExecutor;
import org.opengauss.agent.service.task.core.HostDynamicExecutor;
import org.opengauss.agent.service.task.core.HostStaticExecutor;
import org.opengauss.agent.service.task.core.OsMetricExecutor;
import org.opengauss.agent.service.task.core.OsPipeExecutor;
import java.util.EnumMap;
import java.util.Map;
import java.util.Objects;
/**
* TaskExecutorFactory
*
* @author: wangchao
* @Date: 2025/4/9 09:34
* @Description: TaskExecutorFactory
* @since 7.0.0-RC2
**/
@Unremovable
@ApplicationScoped
public class TaskExecutorFactory {
@Inject
HostStaticExecutor hostStaticExecutor;
@Inject
HostDynamicExecutor hostDynamicExecutor;
@Inject
OsMetricExecutor osMetricExecutor;
@Inject
DbMetricExecutor dbMetricExecutor;
@Inject
OsPipeExecutor osPipeExecutor;
@Inject
DbPipeExecutor dbPipeExecutor;
private final Map<TaskType, TaskExecutor> executors = new EnumMap<>(TaskType.class);
/**
* Initialize the task executor factory.
*/
@PostConstruct
public void init() {
executors.put(TaskType.OSHI_FIXED_METRIC, hostStaticExecutor);
executors.put(TaskType.OSHI_DYNAMIC_METRIC, hostDynamicExecutor);
executors.put(TaskType.OS_METRIC, osMetricExecutor);
executors.put(TaskType.DB_METRIC, dbMetricExecutor);
executors.put(TaskType.OS_PIPE, osPipeExecutor);
executors.put(TaskType.DB_PIPE, dbPipeExecutor);
}
/**
* Get the task executor by task type.
*
* @param taskType task type
* @return task executor
*/
public TaskExecutor getExecutor(TaskType taskType) {
TaskExecutor executor = executors.get(taskType);
if (Objects.isNull(executor)) {
throw new TaskExecutionException("task executor" + taskType + " not found");
}
return executor;
}
}

View File

@ -0,0 +1,123 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.service.task;
import io.quarkus.arc.Unremovable;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import lombok.extern.slf4j.Slf4j;
import org.opengauss.agent.entity.TaskDefinition;
import org.opengauss.agent.entity.TaskDefinitionBuilder;
import org.opengauss.agent.entity.TaskExecution;
import org.opengauss.agent.entity.task.AgentTaskVo;
import org.opengauss.agent.enums.TaskType;
import org.opengauss.agent.exception.AgentException;
import java.time.Instant;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* TaskManager
*
* @author: wangchao
* @Date: 2025/5/21 11:18
* @since 7.0.0-RC2
**/
@Slf4j
@Unremovable
@ApplicationScoped
public class TaskManager {
@Inject
TaskExecutorFactory executorFactory;
private final ConcurrentMap<Long, TaskExecution> taskExecMap = new ConcurrentHashMap<>();
/**
* startTask
*
* @param taskConfig taskConfig
* @return TaskExecution
*/
public TaskExecution startTask(AgentTaskVo taskConfig) {
TaskDefinition taskDef = TaskDefinitionBuilder.builder(taskConfig);
TaskExecution execution = new TaskExecution(taskDef);
if (taskExecMap.containsKey(taskDef.getTaskId())) {
log.info("task {} is already running", taskDef.getTaskId());
execution.setStatus("already_running");
execution.setErrorMessage("task " + taskDef.getTaskId() + " is already running");
return execution;
}
log.info("task {} is running ", taskDef.getTaskName());
try {
TaskExecutor executor = executorFactory.getExecutor(taskDef.getTaskType());
executor.initialize(taskDef);
executor.execute();
execution.setStatus("running");
execution.setStartTime(Instant.now());
taskExecMap.put(taskDef.getTaskId(), execution);
} catch (AgentException e) {
log.error("task {} execute failed", taskDef.getTaskName(), e);
execution.setStatus("no_start");
execution.setErrorMessage(e.getMessage());
}
return execution;
}
/**
* stop task
*
* @param taskId taskId
* @return TaskExecution
*/
public Optional<TaskExecution> stop(Long taskId) {
if (!taskExecMap.containsKey(taskId)) {
log.warn("task {} is not running", taskId);
return Optional.empty();
}
TaskExecution execution = taskExecMap.get(taskId);
TaskType taskType = execution.getTaskType();
if (taskType == null) {
log.warn("task {} type is null", taskId);
return Optional.empty();
}
TaskExecutor executor = executorFactory.getExecutor(taskType);
if (executor == null) {
log.warn("task {} does not have executor for task type {}", taskId, taskType);
return Optional.empty();
}
executor.cancel(taskId);
log.info("stop task {}", taskId);
if (!executor.hasTaskRunning()) {
taskExecMap.remove(taskId);
log.info("task {} does not have executor {} running, remove it", taskId, taskType);
}
execution.setStatus("stop");
execution.setEndTime(Instant.now());
return Optional.of(execution);
}
/**
* check task is empty
*
* @return boolean
*/
public boolean isEmpty() {
return taskExecMap.isEmpty();
}
}

View File

@ -0,0 +1,130 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.service.task.core;
import lombok.Getter;
import org.opengauss.agent.config.AppConfig;
import org.opengauss.agent.entity.TaskDefinition;
import org.opengauss.agent.exception.TaskExecutionException;
import org.opengauss.agent.service.task.group.GroupKey;
import org.opengauss.agent.service.task.group.TaskGroup;
import org.slf4j.Logger;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* CommonExecutor
*
* @author: wangchao
* @Date: 2025/4/9 10:35
* @Description: CommonExecutor
* @since 7.0.0-RC2
**/
public class CommonExecutor {
private final Logger log;
private final AppConfig appConfig;
private final Map<Long, GroupKey> taskIdToGroupKey = new ConcurrentHashMap<>();
@Getter
private final ConcurrentMap<GroupKey, TaskGroup> targetGroups = new ConcurrentHashMap<>();
/**
* CommonExecutor
*
* @param appConfig appConfig
* @param log log
*/
public CommonExecutor(AppConfig appConfig, Logger log) {
this.log = log;
this.appConfig = appConfig;
}
/**
* Initialize task
*
* @param task task
* @throws TaskExecutionException TaskExecutionException
*/
public void initialize(TaskDefinition task) throws TaskExecutionException {
GroupKey key = new GroupKey(task);
taskIdToGroupKey.put(task.getTaskId(), key);
TaskGroup otelTaskGroup = targetGroups.compute(key, (k, group) -> {
if (group == null) {
TaskGroup newGroup = new TaskGroup(key);
newGroup.addTask(task);
newGroup.createContext(appConfig);
return newGroup;
} else {
synchronized (this) {
if (!group.validateTaskConsistency(task)) {
throw new TaskExecutionException("Task conflicts with existing group configuration");
}
group.addTask(task);
group.refreshContext();
}
return group;
}
});
log.info("Initialized task execute environment {} :tasks:{}", key, otelTaskGroup.getTaskIds());
}
/**
* Check if there is a task running
*
* @return boolean
*/
public boolean hasTaskRunning() {
return targetGroups.values().stream().anyMatch(TaskGroup::hasTask);
}
/**
* Remove task
*
* @param taskId Task ID
*/
public void cancal(Long taskId) {
GroupKey groupKey = taskIdToGroupKey.remove(taskId);
if (groupKey == null) {
log.warn("Task {} not found in the executor", taskId);
return;
}
TaskGroup group = targetGroups.get(groupKey);
if (group == null) {
log.warn("Group for task {} not found in the executor", taskId);
return;
}
synchronized (group) {
group.removeTask(taskId);
if (group.isEmpty()) {
targetGroups.remove(groupKey);
log.info("Removed group {} due to no tasks", groupKey);
}
}
}
/**
* Stop all tasks
*/
public void stop() {
targetGroups.forEach((target, group) -> {
group.stopPeriodicCollection();
});
targetGroups.clear();
taskIdToGroupKey.clear();
}
}

View File

@ -0,0 +1,231 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.service.task.core;
import io.quarkus.runtime.ShutdownEvent;
import io.quarkus.runtime.StartupEvent;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
import jakarta.inject.Inject;
import lombok.extern.slf4j.Slf4j;
import org.opengauss.agent.config.AppConfig;
import org.opengauss.agent.entity.TaskDefinition;
import org.opengauss.agent.entity.task.AgentClusterVo;
import org.opengauss.agent.entity.task.TaskMetricsDefinitionVo;
import org.opengauss.agent.enums.DatabaseType;
import org.opengauss.agent.exception.TaskExecutionException;
import org.opengauss.agent.service.task.TaskExecutor;
import org.opengauss.agent.service.task.group.GroupKey;
import org.opengauss.agent.service.task.group.OtelTaskGroup;
import org.opengauss.agent.utils.RsaUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* DbMetricExecutor
*
* @author: wangchao
* @Date: 2025/4/9 10:35
* @Description: DbMetricExecutor
* @since 7.0.0-RC2
**/
@Slf4j
@ApplicationScoped
public class DbMetricExecutor implements TaskExecutor {
OtelCommonExecutor otelCommonExecutor;
@Inject
AppConfig appConfig;
/**
* initialize DbMetricExecutor instance when application start
*
* @param event startup event
*/
void onStart(@Observes StartupEvent event) {
otelCommonExecutor = new OtelCommonExecutor(appConfig, log);
}
@Override
public void initialize(TaskDefinition task) throws TaskExecutionException {
log.info("initialize task: {}", task);
otelCommonExecutor.initialize(task);
}
@Override
public void execute() throws TaskExecutionException {
ConcurrentMap<GroupKey, OtelTaskGroup> targetGroups = otelCommonExecutor.getTargetGroups();
targetGroups.forEach((target, group) -> {
if (!group.hasTask()) {
log.warn("No tasks configured for group {}", target);
return;
}
// start schedule
startGroupTask(target, group);
});
}
private void startGroupTask(GroupKey key, OtelTaskGroup group) {
log.info("Starting group task: {}:{}", key, group.getTaskIds());
group.startGroupTask(new DbMetricsCollectorTask(group, key));
}
static class DbMetricsCollectorTask implements Runnable {
private final OtelTaskGroup group;
private final GroupKey key;
/**
* DbMetricsCollectorTask
*
* @param group OtelTaskGroup
* @param key GroupKey
*/
DbMetricsCollectorTask(OtelTaskGroup group, GroupKey key) {
this.group = group;
this.key = key;
}
@Override
public void run() {
MeterProviderContext meterProviderContext = group.getMeterProviderContext();
if (meterProviderContext == null) {
log.warn("MeterProviderContext is null for group {}", key);
return;
}
Map<String, Double> dynamicMetrics = new ConcurrentHashMap<>();
group.getTasks().forEach((taskId, taskDefinition) -> {
collectSingleTaskMetrics(key, taskId, taskDefinition, dynamicMetrics);
});
meterProviderContext.refreshDynamicMetrics(dynamicMetrics);
log.info("Successfully collected {} db metrics for group [{}, tasks={}] collectors", dynamicMetrics.size(),
key, group.getTaskIds());
meterProviderContext.exportMetrics();
}
/**
* collect metrics for single task
*
* @param key group key
* @param taskId task id
* @param taskDefinition task definition
* @param dynamicMetrics dynamic metrics
*/
private void collectSingleTaskMetrics(GroupKey key, Long taskId, TaskDefinition taskDefinition,
Map<String, Double> dynamicMetrics) {
AgentClusterVo clusterConfig = taskDefinition.getClusterConfig();
try (Connection connection = createConnection(clusterConfig);) {
processMetricsDefinition(taskDefinition, connection, dynamicMetrics);
} catch (SQLException e) {
handleDatabaseError(key, taskId, clusterConfig, e);
} catch (Exception ex) {
log.error("Metric collection failed for group {}", key.getDataSendTarget(), ex);
}
}
/**
* handle metrics definition
*
* @param taskDefinition task definition
* @param connection database connection
* @param dynamicMetrics dynamic metrics
*/
private void processMetricsDefinition(TaskDefinition taskDefinition, Connection connection,
Map<String, Double> dynamicMetrics) {
List<TaskMetricsDefinitionVo> list = taskDefinition.getMetricsDefinitionList();
list.forEach(metric -> {
processMetric(connection, metric, dynamicMetrics);
});
}
/**
* execute metric collect sql and collect metrics
*
* @param connection database connection
* @param metricDef metric definition
* @param dynamicMetrics dynamic metrics
*/
private void processMetric(Connection connection, TaskMetricsDefinitionVo metricDef,
Map<String, Double> dynamicMetrics) {
String name = metricDef.getName();
String cmd = metricDef.getCollectCmd();
try (PreparedStatement ps = connection.prepareStatement(cmd); ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
dynamicMetrics.put(name, rs.getDouble(metricDef.getFieldName()));
} else {
log.warn("No data queried for metrics: {}", metricDef);
}
} catch (SQLException e) {
handleMetricError(name, e);
}
}
private void handleMetricError(String name, SQLException e) {
log.error("Metric collection error: Metric: {} Error Code: {} SQL State: {} Message: {}", name,
e.getErrorCode(), e.getSQLState(), e.getMessage());
}
private void handleDatabaseError(GroupKey key, Long taskId, AgentClusterVo clusterConfig, SQLException e) {
log.error("Database error occurred: Group: {} Task ID: {} Database: {} URL: {} "
+ "Error Code: {} SQL State: {} Message: {}", key, taskId, clusterConfig.getDataBaseType(),
clusterConfig.getUrl(), e.getErrorCode(), e.getSQLState(), e.getMessage());
}
/**
* Create a connection to the database.
*
* @param clusterConfig the cluster configuration
* @return the connection
* @throws SQLException if a database access error occurs
*/
private Connection createConnection(AgentClusterVo clusterConfig) throws SQLException {
return DatabaseType.valueOf(clusterConfig.getDataBaseType())
.getConnection(clusterConfig.getUrl(), clusterConfig.getUsername(),
RsaUtils.decrypt(clusterConfig.getDbPassword()));
}
}
/**
* Stop the executor.
*
* @param event the shutdown event
*/
void onStop(@Observes ShutdownEvent event) {
otelCommonExecutor.stop();
DatabaseType.shutdownAll();
}
@Override
public boolean hasTaskRunning() {
return otelCommonExecutor.hasTaskRunning();
}
/**
* Remove task
*
* @param taskId Task ID
*/
@Override
public void cancel(Long taskId) {
otelCommonExecutor.cancal(taskId);
}
}

View File

@ -0,0 +1,221 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.service.task.core;
import cn.hutool.core.collection.CollUtil;
import io.quarkus.runtime.StartupEvent;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
import jakarta.inject.Inject;
import lombok.extern.slf4j.Slf4j;
import org.opengauss.agent.config.AppConfig;
import org.opengauss.agent.entity.TaskDefinition;
import org.opengauss.agent.entity.task.AgentClusterVo;
import org.opengauss.agent.entity.task.TaskMetricsDefinitionVo;
import org.opengauss.agent.enums.DatabaseType;
import org.opengauss.agent.exception.TaskExecutionException;
import org.opengauss.agent.service.task.TaskExecutor;
import org.opengauss.agent.service.task.group.GroupKey;
import org.opengauss.agent.service.task.group.TaskGroup;
import org.opengauss.agent.utils.RsaUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentMap;
/**
* DbPipeExecutor
*
* @author: wangchao
* @Date: 2025/4/9 10:43
* @Description: SqlPipelineExecutor
* @since 7.0.0-RC2
**/
@Slf4j
@ApplicationScoped
public class DbPipeExecutor implements TaskExecutor {
@Inject
AppConfig appConfig;
private CommonExecutor commonExecutor;
/**
* initialize DbPipeExecutor instance when application start
*
* @param event startup event
*/
void onStart(@Observes StartupEvent event) {
commonExecutor = new CommonExecutor(appConfig, log);
}
@Override
public void initialize(TaskDefinition task) throws TaskExecutionException {
commonExecutor.initialize(task);
}
@Override
public void execute() throws TaskExecutionException {
ConcurrentMap<GroupKey, TaskGroup> targetGroups = commonExecutor.getTargetGroups();
targetGroups.forEach((groupKey, group) -> {
if (!group.hasTask()) {
log.warn("No tasks configured for group {}", groupKey);
return;
}
// start schedule
group.startGroupTask(new DatabasePipeTask(groupKey, group));
});
}
@Override
public void cancel(Long taskId) {
commonExecutor.cancal(taskId);
}
@Override
public boolean hasTaskRunning() {
return false;
}
static class DatabasePipeTask implements Runnable {
private static final String NAN = "NaN";
private final GroupKey groupKey;
private final TaskGroup group;
public DatabasePipeTask(GroupKey groupKey, TaskGroup group) {
this.groupKey = groupKey;
this.group = group;
}
@Override
public void run() {
group.getTasks().forEach((taskId, taskDefinition) -> {
List<Map<String, Object>> taskPipeData = collectSingleTaskMetrics(taskId, taskDefinition);
pushDataToAgentPipe(taskId, taskPipeData);
log.info("Successfully collected {} db metrics for group [{}, task={}] collectors", taskPipeData.size(),
groupKey, taskId);
});
}
private void pushDataToAgentPipe(Long taskId, List<Map<String, Object>> taskPipeData) {
if (CollUtil.isNotEmpty(taskPipeData)) {
group.sendData(taskId, taskPipeData);
}
}
/**
* collect metrics for single task
*
* @param taskId task id
* @param taskDefinition task definition
* @return metrics data list
*/
private List<Map<String, Object>> collectSingleTaskMetrics(Long taskId, TaskDefinition taskDefinition) {
AgentClusterVo clusterConfig = taskDefinition.getClusterConfig();
List<Map<String, Object>> dataList = new LinkedList<>();
try (Connection connection = createConnection(clusterConfig);) {
processMetricsDefinition(taskDefinition, connection, dataList);
} catch (SQLException e) {
handleDatabaseError(taskId, clusterConfig, e);
} catch (Exception ex) {
log.error("Pipe Metric collection failed for group {}", groupKey.getDataSendTarget(), ex);
}
return dataList;
}
private void handleDatabaseError(Long taskId, AgentClusterVo clusterConfig, SQLException e) {
log.error("Database error occurred: Group: {} Task ID: {} Database: {} URL: {} "
+ "Error Code: {} SQL State: {} Message: {}", groupKey, taskId, clusterConfig.getDataBaseType(),
clusterConfig.getUrl(), e.getErrorCode(), e.getSQLState(), e.getMessage());
}
/**
* handle metrics definition
*
* @param taskDefinition task definition
* @param connection database connection
* @param dataList dataList
*/
private void processMetricsDefinition(TaskDefinition taskDefinition, Connection connection,
List<Map<String, Object>> dataList) {
List<TaskMetricsDefinitionVo> list = taskDefinition.getMetricsDefinitionList();
String execQuerySql = taskDefinition.getOperateObj();
try (PreparedStatement ps = connection.prepareStatement(execQuerySql); ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
Map<String, Object> rowData = createRowData(list, rs);
dataList.add(rowData);
}
} catch (SQLException e) {
handleMetricError(taskDefinition.getTaskName(), e);
}
}
private Map<String, Object> createRowData(List<TaskMetricsDefinitionVo> metricsDefinitions, ResultSet rs)
throws SQLException {
Map<String, Object> rowData = new HashMap<>();
for (TaskMetricsDefinitionVo metricDef : metricsDefinitions) {
Object value = getValueFromResultSet(metricDef, rs);
rowData.put(metricDef.getName(), Optional.ofNullable(value).orElse(NAN));
}
return rowData;
}
private Object getValueFromResultSet(TaskMetricsDefinitionVo metricDef, ResultSet rs) {
String fieldName = metricDef.getFieldName();
String dataType = Optional.ofNullable(metricDef.getDataType()).orElse("STRING").toUpperCase(Locale.ROOT);
Object result = null;
try {
result = switch (dataType) {
case "INT" -> rs.getInt(fieldName);
case "LONG" -> rs.getLong(fieldName);
case "DOUBLE" -> rs.getDouble(fieldName);
case "STRING" -> rs.getString(fieldName);
default -> rs.getObject(fieldName);
};
} catch (SQLException e) {
log.error("Error retrieving value for task.field -> {}.{} :{}", metricDef.getName(), fieldName,
e.getMessage());
}
return result;
}
private void handleMetricError(String name, SQLException e) {
log.error("Pipe Metric collection error: task: {} Error Code: {} SQL State: {} Message: {}", name,
e.getErrorCode(), e.getSQLState(), e.getMessage());
}
/**
* Create a connection to the database.
*
* @param clusterConfig the cluster configuration
* @return the connection
* @throws SQLException if a database access error occurs
*/
private Connection createConnection(AgentClusterVo clusterConfig) throws SQLException {
return DatabaseType.valueOf(clusterConfig.getDataBaseType())
.getConnection(clusterConfig.getUrl(), clusterConfig.getUsername(),
RsaUtils.decrypt(clusterConfig.getDbPassword()));
}
}
}

View File

@ -0,0 +1,130 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.service.task.core;
import io.quarkus.runtime.ShutdownEvent;
import io.quarkus.runtime.StartupEvent;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
import jakarta.inject.Inject;
import lombok.extern.slf4j.Slf4j;
import org.opengauss.agent.config.AppConfig;
import org.opengauss.agent.entity.TaskDefinition;
import org.opengauss.agent.exception.AgentException;
import org.opengauss.agent.exception.TaskExecutionException;
import org.opengauss.agent.service.task.TaskExecutor;
import org.opengauss.agent.service.task.group.GroupKey;
import org.opengauss.agent.service.task.group.OtelTaskGroup;
import org.opengauss.agent.vo.MultiValueMetric;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
/**
* HostDynamicExecutor
*
* @author: wangchao
* @Date: 2025/4/9 10:35
* @Description: HostStaticCollector
* @since 7.0.0-RC2
**/
@Slf4j
@ApplicationScoped
public class HostDynamicExecutor implements TaskExecutor {
@Inject
AppConfig appConfig;
private OtelCommonExecutor otelCommonExecutor;
private final OshiServerCollector oshiServerCollector = new OshiServerCollector();
/**
* start
*
* @param event startup event
*/
void onStart(@Observes StartupEvent event) {
otelCommonExecutor = new OtelCommonExecutor(appConfig, log);
}
@Override
public void initialize(TaskDefinition task) throws TaskExecutionException {
otelCommonExecutor.initialize(task);
}
@Override
public void execute() throws TaskExecutionException {
ConcurrentMap<GroupKey, OtelTaskGroup> targetGroups = otelCommonExecutor.getTargetGroups();
targetGroups.forEach((target, group) -> {
if (!group.hasTask()) {
log.warn("No tasks configured for group {}", target);
return;
}
startGroupTask(target, group);
});
}
private void startGroupTask(GroupKey key, OtelTaskGroup group) {
log.info("Starting group task: {}:{}", key, group.getTaskIds());
group.startGroupTask(() -> collectMetricsForGroup(group, key));
}
private void collectMetricsForGroup(OtelTaskGroup group, GroupKey key) {
try {
Set<Long> taskSet = group.getTaskIds();
MeterProviderContext meterProviderContext = group.getMeterProviderContext();
// 实际收集逻辑
if (meterProviderContext != null) {
List<String> collectors = group.getMergedCollectorList();
Map<String, Double> dynamicMetrics = oshiServerCollector.dynamicCollectMap(collectors);
List<MultiValueMetric> multiMetrics = oshiServerCollector.multiDynamicCollect(collectors);
log.info("Successfully collected {} host metrics and {} host multi-metrics for group [{}, tasks={}] "
+ "collectors ->{} ", dynamicMetrics.size(), multiMetrics.size(), key, taskSet, collectors.size());
meterProviderContext.refreshDynamicMetrics(dynamicMetrics);
meterProviderContext.refreshMultiDynamicMetrics(multiMetrics, taskSet);
meterProviderContext.exportMetrics();
}
} catch (AgentException ex) {
log.error("Metric collection failed for group {}", key.getDataSendTarget(), ex);
}
}
/**
* stop
*
* @param event shutdown event
*/
void onStop(@Observes ShutdownEvent event) {
otelCommonExecutor.stop();
}
@Override
public boolean hasTaskRunning() {
return otelCommonExecutor.hasTaskRunning();
}
/**
* Remove task
*
* @param taskId Task ID
*/
@Override
public void cancel(Long taskId) {
otelCommonExecutor.cancal(taskId);
}
}

View File

@ -0,0 +1,61 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.service.task.core;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.opengauss.agent.client.HostFixedMetricsClient;
import org.opengauss.agent.client.ServerClientFactory;
import org.opengauss.agent.entity.HostBaseInfo;
import org.opengauss.agent.entity.TaskDefinition;
import org.opengauss.agent.exception.TaskExecutionException;
import org.opengauss.agent.service.task.TaskExecutor;
/**
* HostStaticExecutor
*
* @author: wangchao
* @Date: 2025/4/9 10:35
* @Description: HostStaticCollector
* @since 7.0.0-RC2
**/
@ApplicationScoped
public class HostStaticExecutor implements TaskExecutor {
HostFixedMetricsClient hostFixedMetricsClient;
@Inject
ServerClientFactory clientFactory;
@Override
public void initialize(TaskDefinition taskDefinition) throws TaskExecutionException {
hostFixedMetricsClient = clientFactory.createHostFixedMetricsClient();
}
@Override
public void execute() throws TaskExecutionException {
HostBaseInfo hostBaseInfo = OshiServerCollector.fixedCollect();
hostFixedMetricsClient.sendHostBaseInfo("", hostBaseInfo);
}
@Override
public void cancel(Long taskId) {
}
@Override
public boolean hasTaskRunning() {
return false;
}
}

View File

@ -0,0 +1,231 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.service.task.core;
import cn.hutool.core.lang.Pair;
import cn.hutool.core.util.StrUtil;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.api.metrics.ObservableDoubleGauge;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.metrics.SdkMeterProvider;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.opengauss.agent.entity.task.TaskMetricsDefinitionVo;
import org.opengauss.agent.service.otel.HttpMetricExporter;
import org.opengauss.agent.vo.MetricData;
import org.opengauss.agent.vo.MetricKey;
import org.opengauss.agent.vo.MultiValueMetric;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* MeterProviderContext
*
* @author: wangchao
* @Date: 2025/5/16 16:43
* @since 7.0.0-RC2
**/
@Slf4j
public class MeterProviderContext {
@Getter
boolean isRegister = false;
final OpenTelemetrySdk sdk;
final SdkMeterProvider meterProvider;
final HttpMetricExporter exporter;
final Meter meter;
ConcurrentMap<MetricKey, MetricData> gaugesMetricDataMap = new ConcurrentHashMap<>();
ConcurrentMap<MetricKey, ObservableDoubleGauge> gaugesMap = new ConcurrentHashMap<>();
List<TaskMetricsDefinitionVo> metricsDefinitions;
List<String> collectors;
Map<Long, List<MetricKey>> taskMetricKeys = new ConcurrentHashMap<>();
/**
* Constructor for MeterProviderContext
*
* @param sdk OpenTelemetrySdk
* @param meterProvider meter provider
* @param exporter exporter
* @param meter meter
*/
public MeterProviderContext(OpenTelemetrySdk sdk, SdkMeterProvider meterProvider, HttpMetricExporter exporter,
Meter meter) {
this.sdk = sdk;
this.meterProvider = meterProvider;
this.exporter = exporter;
this.meter = meter;
}
/**
* Initialize the MeterProviderContext
*
* @param metrics metrics
* @param collectors collectors
* @param taskMetricKeys taskMetricKeys
*/
public void initialize(List<TaskMetricsDefinitionVo> metrics, List<String> collectors,
Map<Long, List<MetricKey>> taskMetricKeys) {
this.metricsDefinitions = metrics;
this.collectors = collectors;
this.taskMetricKeys = taskMetricKeys;
registerMetrics();
}
/**
* Register metrics
*/
void registerMetrics() {
isRegister = true;
for (TaskMetricsDefinitionVo metric : metricsDefinitions) {
String prop = metric.getProp();
if (StrUtil.isEmpty(prop)) {
MetricKey metricKey = MetricKey.of(metric.getName());
gaugesMetricDataMap.put(metricKey, new MetricData());
gaugesMap.put(metricKey, meter.gaugeBuilder(metric.getName())
.setUnit(Objects.isNull(metric.getUnit()) ? "" : metric.getUnit())
.setDescription(Objects.isNull(metric.getDescription()) ? "" : metric.getDescription())
.buildWithCallback(measurement -> {
MetricData data = gaugesMetricDataMap.get(metricKey);
measurement.record(data.getValue().get());
}));
}
}
}
/**
* Refresh dynamic metrics value
*
* @param dynamicMetrics dynamic metrics
*/
public void refreshDynamicMetrics(Map<String, Double> dynamicMetrics) {
if (dynamicMetrics == null || dynamicMetrics.isEmpty()) {
return;
}
dynamicMetrics.forEach((metricName, value) -> {
MetricKey metricKey = MetricKey.of(metricName);
MetricData metricData = gaugesMetricDataMap.get(metricKey);
if (metricData != null) {
metricData.getValue().set(value);
} else {
log.warn("============== Metric {} not found in gaugesMetricDataMap ===============", metricName);
}
});
}
/**
* Refresh multi value dynamic metrics
*
* @param multiDynamicMetrics multi dynamic metrics
* @param activeTaskIds active task ids
*/
public void refreshMultiDynamicMetrics(List<MultiValueMetric> multiDynamicMetrics, Set<Long> activeTaskIds) {
// 清理无效任务指标
cleanupStaleMetrics(activeTaskIds);
// 更新当前指标
Map<String, TaskMetricsDefinitionVo> metricsDefinitionVoMap = metricsDefinitions.stream()
.collect(Collectors.toMap(TaskMetricsDefinitionVo::getName, Function.identity()));
multiDynamicMetrics.forEach(metricValue -> {
updateOtelMetrics(metricsDefinitionVoMap.get(metricValue.getName()), metricValue.getPropValue(),
metricValue.getPropValue().getKey());
});
}
/**
* Update otel metrics
*
* @param metric metric
* @param doublePair value
* @param propValue property value
*/
void updateOtelMetrics(TaskMetricsDefinitionVo metric, Pair<String, Double> doublePair, String propValue) {
// 仅更新数值不重建指标
MetricKey key = MetricKey.of(metric.getName(), propValue);
// 根据属性值动态创建或者更新指标实例
MetricData metricData = gaugesMetricDataMap.computeIfAbsent(key, k -> {
// 动态注册新属性实例
ObservableDoubleGauge gauge = meter.gaugeBuilder(metric.getName())
.setUnit(metric.getUnit())
.setDescription(metric.getDescription())
.buildWithCallback(measurement -> {
MetricData data = gaugesMetricDataMap.get(key);
measurement.record(data.getValue().get(), data.getAttributes());
});
gaugesMap.put(key, gauge);
return new MetricData();
});
// 更新数值和属性
metricData.getValue().set(doublePair.getValue());
if (StrUtil.isNotEmpty(propValue)) {
metricData.setAttributes(Attributes.of(AttributeKey.stringKey(metric.getProp()), propValue));
}
}
/**
* Cleanup stale metrics
*
* @param activeTaskIds the active task ids
*/
void cleanupStaleMetrics(Set<Long> activeTaskIds) {
Set<Long> staleTasks = new HashSet<>(taskMetricKeys.keySet());
staleTasks.removeAll(activeTaskIds);
staleTasks.forEach(taskId -> {
taskMetricKeys.get(taskId).forEach(gaugesMetricDataMap::remove);
taskMetricKeys.remove(taskId);
});
}
/**
* Shutdown the MeterProviderContext
*/
public void shutdown() {
gaugesMap.forEach((metric, metricGauges) -> {
metricGauges.close();
});
gaugesMetricDataMap.clear();
gaugesMap.clear();
if (exporter != null) {
exporter.shutdown().join(100, TimeUnit.MILLISECONDS);
}
if (meterProvider != null) {
meterProvider.shutdown().join(100, TimeUnit.MILLISECONDS);
}
if (sdk != null) {
sdk.shutdown().join(100, TimeUnit.MILLISECONDS);
}
collectors.clear();
metricsDefinitions.clear();
taskMetricKeys.clear();
isRegister = false;
}
/**
* Export metrics
*/
public void exportMetrics() {
exporter.flush();
}
}

View File

@ -0,0 +1,153 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.service.task.core;
import cn.hutool.core.util.StrUtil;
import io.quarkus.runtime.ShutdownEvent;
import io.quarkus.runtime.StartupEvent;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
import jakarta.inject.Inject;
import lombok.extern.slf4j.Slf4j;
import org.opengauss.agent.config.AppConfig;
import org.opengauss.agent.entity.OsCmdResult;
import org.opengauss.agent.entity.TaskDefinition;
import org.opengauss.agent.entity.task.TaskMetricsDefinitionVo;
import org.opengauss.agent.exception.TaskExecutionException;
import org.opengauss.agent.service.task.TaskExecutor;
import org.opengauss.agent.service.task.group.GroupKey;
import org.opengauss.agent.service.task.group.OtelTaskGroup;
import org.opengauss.agent.utils.MathUtils;
import org.opengauss.agent.utils.OsCommandUtils;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* OsMetricExecutor
*
* @author: wangchao
* @Date: 2025/4/9 10:35
* @Description: OsMetricExecutor
* @since 7.0.0-RC2
**/
@Slf4j
@ApplicationScoped
public class OsMetricExecutor implements TaskExecutor {
OtelCommonExecutor otelCommonExecutor;
@Inject
AppConfig appConfig;
/**
* initialize OsMetricExecutor instance when application start
*
* @param event startup event
*/
void onStart(@Observes StartupEvent event) {
otelCommonExecutor = new OtelCommonExecutor(appConfig, log);
log.info("initialize os metric task executor : {}", appConfig);
}
@Override
public void initialize(TaskDefinition task) throws TaskExecutionException {
log.info("initialize os metric task: {}", task);
otelCommonExecutor.initialize(task);
}
@Override
public void execute() throws TaskExecutionException {
ConcurrentMap<GroupKey, OtelTaskGroup> targetGroups = otelCommonExecutor.getTargetGroups();
targetGroups.forEach((target, group) -> {
if (!group.hasTask()) {
log.warn("No os metric tasks configured for group {}", target);
return;
}
// start schedule
startGroupTaskOfOsMetricExecutor(target, group);
});
}
private void startGroupTaskOfOsMetricExecutor(GroupKey key, OtelTaskGroup group) {
log.info("Starting group task: {}:{}", key, group.getTaskIds());
group.startGroupTask(new OsMetricsCollectorTask(group));
}
@Override
public void cancel(Long taskId) {
otelCommonExecutor.cancal(taskId);
}
/**
* Stop the executor.
*
* @param event the shutdown event
*/
void onStop(@Observes ShutdownEvent event) {
otelCommonExecutor.stop();
}
@Override
public boolean hasTaskRunning() {
return otelCommonExecutor.hasTaskRunning();
}
static class OsMetricsCollectorTask implements Runnable {
private final GroupKey groupKey;
private final OtelTaskGroup group;
public OsMetricsCollectorTask(OtelTaskGroup group) {
this.groupKey = group.getGroupKey();
this.group = group;
}
@Override
public void run() {
MeterProviderContext meterProviderContext = group.getMeterProviderContext();
if (meterProviderContext == null) {
log.warn("MeterProviderContext is null for group {}", groupKey);
return;
}
Map<String, Double> dynamicMetrics = new ConcurrentHashMap<>();
group.getTasks().forEach((taskId, taskDefinition) -> {
collectTaskMetrics(taskDefinition, dynamicMetrics);
});
meterProviderContext.refreshDynamicMetrics(dynamicMetrics);
log.info("Successfully collected {} os metrics for group [{}, tasks={}] collectors", dynamicMetrics.size(),
groupKey, group.getTaskIds());
meterProviderContext.exportMetrics();
}
private void collectTaskMetrics(TaskDefinition taskDefinition, Map<String, Double> dynamicMetrics) {
if (StrUtil.isNotEmpty(taskDefinition.getOperateObj())) {
log.warn("not support os task collect {},it must be metric collect {} . task={}",
taskDefinition.getTaskName(), taskDefinition.getOperateObj(), taskDefinition.getTaskId());
}
List<TaskMetricsDefinitionVo> metricsDefinitionList = taskDefinition.getMetricsDefinitionList();
for (TaskMetricsDefinitionVo definitionVo : metricsDefinitionList) {
OsCmdResult res = OsCommandUtils.execute(definitionVo.getCollectCmd());
if (res.isSuccess()) {
dynamicMetrics.put(definitionVo.getName(), MathUtils.doubleValueOf(res.output()));
} else {
log.error("Failed to collect os metric {} {} for task {}: {}", definitionVo.getName(),
definitionVo.getCollectCmd(), taskDefinition.getTaskId(), res.output());
}
}
}
}
}

View File

@ -0,0 +1,50 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.service.task.core;
import jakarta.enterprise.context.ApplicationScoped;
import org.opengauss.agent.entity.TaskDefinition;
import org.opengauss.agent.exception.TaskExecutionException;
import org.opengauss.agent.service.task.TaskExecutor;
/**
* OsPipeExecutor
*
* @author: wangchao
* @Date: 2025/4/9 10:44
* @Description: OsPipeExecutor
* @since 7.0.0-RC2
**/
@ApplicationScoped
public class OsPipeExecutor implements TaskExecutor {
@Override
public void initialize(TaskDefinition taskDefinition) throws TaskExecutionException {
}
@Override
public void execute() throws TaskExecutionException {
}
@Override
public void cancel(Long taskId) {
}
@Override
public boolean hasTaskRunning() {
return false;
}
}

View File

@ -0,0 +1,367 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.service.task.core;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Pair;
import lombok.extern.slf4j.Slf4j;
import oshi.SystemInfo;
import oshi.hardware.CentralProcessor;
import oshi.hardware.GlobalMemory;
import oshi.hardware.HWDiskStore;
import oshi.hardware.NetworkIF;
import oshi.software.os.OSFileStore;
import oshi.software.os.OperatingSystem;
import oshi.util.Util;
import org.opengauss.agent.entity.HostBaseInfo;
import org.opengauss.agent.utils.MathUtils;
import org.opengauss.agent.vo.MultiValueMetric;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* OshiServerCollector
*
* @author: wangchao
* @Date: 2025/4/8 17:04
* @Description: OshiServerCollector
* @since 7.0.0-RC2
**/
@Slf4j
public class OshiServerCollector {
private static final SystemInfo SYSTEM_INFO = new SystemInfo();
private static final CentralProcessor CPU = SYSTEM_INFO.getHardware().getProcessor();
private static final OperatingSystem OS = SYSTEM_INFO.getOperatingSystem();
private static final GlobalMemory MEMORY = SYSTEM_INFO.getHardware().getMemory();
private static final List<HWDiskStore> DISKS = SYSTEM_INFO.getHardware().getDiskStores();
private static final List<OSFileStore> FILE_STORES = SYSTEM_INFO.getOperatingSystem()
.getFileSystem()
.getFileStores();
private static final List<NetworkIF> NETWORK_IFS = SYSTEM_INFO.getHardware().getNetworkIFs();
private final List<DynamicCollector> collectors = Arrays.asList(new MemoryTotalCollector(), new CpuUsageCollector(),
new MemoryAvailableCollector(), new MemoryUsageCollector());
private final List<MultiDynamicCollector> multiCollectors = Arrays.asList(new NetMultiDynamicCollector(),
new DiskMultiDynamicCollector(), new FileStoreMultiDynamicCollector());
/**
* collect host base info
*
* @return HostBaseInfo
*/
public static HostBaseInfo fixedCollect() {
// 主机名
String hostName = OS.getNetworkParams().getHostName();
// CPU 信息
String cpuModel = CPU.getProcessorIdentifier().getName();
long cpuFreq = CPU.getProcessorIdentifier().getVendorFreq();
int physicalCores = CPU.getPhysicalProcessorCount();
int logicalCores = CPU.getLogicalProcessorCount();
String architecture = CPU.getProcessorIdentifier().getMicroarchitecture();
// 操作系统信息
String osName = OS.getFamily();
String osVersion = OS.getVersionInfo().getVersion();
String osBuild = OS.getVersionInfo().getBuildNumber();
return HostBaseInfo.builder()
.hostName(hostName)
.cpuModel(cpuModel)
.cpuFreq(cpuFreq)
.cpuArchitecture(architecture)
.physicalCores(physicalCores)
.logicalCores(logicalCores)
.osName(osName)
.osVersion(osVersion)
.osBuild(osBuild)
.build();
}
/**
* collect dynamic info
*
* @param collectorList collector list
* @return dynamic info
*/
public Map<String, Double> dynamicCollectMap(List<String> collectorList) {
Map<String, Double> result = new HashMap<>();
collectors.stream().filter(collector -> collectorList.contains(collector.getName())).forEach(collector -> {
result.put(collector.getName(), collector.collect().getValue());
});
return result;
}
/**
* multi dynamic collect
*
* @param collectorList collector list
* @return multi value metric list
*/
public List<MultiValueMetric> multiDynamicCollect(List<String> collectorList) {
List<MultiValueMetric> result = new ArrayList<>();
multiCollectors.stream().filter(collector -> collector.containsAny(collectorList)).forEach(collector -> {
result.addAll(collector.collect());
});
return result;
}
/**
* dynamic collect interface
*/
private interface DynamicCollector {
/**
* dynamic collect
*
* @return result
*/
Pair<String, Double> collect();
/**
* collector metric name
*
* @return name
*/
String getName();
}
/**
* multi dynamic collect interface
*/
private interface MultiDynamicCollector {
/**
* collect data
*
* @return result
*/
List<MultiValueMetric> collect();
/**
* check target names contains any metric name in collector
*
* @param targetNames target names
* @return true if contains any metric name
*/
boolean containsAny(List<String> targetNames);
}
/**
* Net dynamic collector
*/
private static class NetMultiDynamicCollector implements MultiDynamicCollector {
private static final int INTERVAL_MILLIS = 1000;
private static final List<String> NAMES = List.of("system.net.bytes.received", "system.net.bytes.sent");
@Override
public List<MultiValueMetric> collect() {
// 记录初始值
long[] initialRecv = new long[NETWORK_IFS.size()];
long[] initialSent = new long[NETWORK_IFS.size()];
for (int i = 0; i < NETWORK_IFS.size(); i++) {
NetworkIF netIf = NETWORK_IFS.get(i);
if (!netIf.isConnectorPresent() || netIf.getName().startsWith("lo")) {
continue;
}
netIf.updateAttributes();
initialRecv[i] = netIf.getBytesRecv();
initialSent[i] = netIf.getBytesSent();
}
// 等待采样间隔
Util.sleep(INTERVAL_MILLIS);
List<MultiValueMetric> list = new ArrayList<>();
// 计算流量差值
for (int i = 0; i < NETWORK_IFS.size(); i++) {
NetworkIF netIf = NETWORK_IFS.get(i);
if (!netIf.isConnectorPresent() || netIf.getName().startsWith("lo")) {
continue;
}
netIf.updateAttributes();
double recv = MathUtils.divide1024(netIf.getBytesRecv() - initialRecv[i]);
double sent = MathUtils.divide1024(netIf.getBytesSent() - initialSent[i]);
list.add(MultiValueMetric.of(getName(0), netIf.getName(), recv));
list.add(MultiValueMetric.of(getName(1), netIf.getName(), sent));
}
return list;
}
private String getName(int index) {
return NAMES.get(index);
}
@Override
public boolean containsAny(List<String> targetNames) {
if (CollUtil.isEmpty(targetNames)) {
return false;
}
return !Collections.disjoint(NAMES, targetNames);
}
}
/**
* Disk dynamic collector
*/
private static class DiskMultiDynamicCollector implements MultiDynamicCollector {
private static final List<String> NAMES = List.of("system.disk.bytes.read", "system.disk.bytes.write",
"system.disk.total");
@Override
public List<MultiValueMetric> collect() {
List<MultiValueMetric> list = new ArrayList<>();
DISKS.forEach(disk -> {
String diskName = disk.getName();
list.add(MultiValueMetric.of(getName(0), diskName, disk.getWriteBytes() / 1e9));
list.add(MultiValueMetric.of(getName(1), diskName, disk.getReadBytes() / 1e9));
list.add(MultiValueMetric.of(getName(2), diskName, disk.getSize() / 1e9));
});
return list;
}
private String getName(int index) {
return NAMES.get(index);
}
@Override
public boolean containsAny(List<String> targetNames) {
if (CollUtil.isEmpty(targetNames)) {
return false;
}
return !Collections.disjoint(NAMES, targetNames);
}
}
/**
* FileStore dynamic collector
*/
private static class FileStoreMultiDynamicCollector implements MultiDynamicCollector {
private static final List<String> NAMES = List.of("system.disk.fs.total", "system.disk.fs.used",
"system.disk.fs.free");
@Override
public List<MultiValueMetric> collect() {
List<MultiValueMetric> list = new ArrayList<>();
FILE_STORES.forEach(fs -> {
String mountPoint = fs.getMount().replace("\\", "");
list.add(MultiValueMetric.of(getName(0), mountPoint, fs.getTotalSpace() / 1e9));
list.add(MultiValueMetric.of(getName(1), mountPoint, fs.getUsableSpace() / 1e9));
list.add(MultiValueMetric.of(getName(2), mountPoint, fs.getFreeSpace() / 1e9));
});
return list;
}
private String getName(int index) {
return NAMES.get(index);
}
@Override
public boolean containsAny(List<String> targetNames) {
if (CollUtil.isEmpty(targetNames)) {
return false;
}
return !Collections.disjoint(NAMES, targetNames);
}
}
/**
* CpuUsageCollector
*/
private static class CpuUsageCollector implements DynamicCollector {
private static final int INTERVAL_MILLIS = 1000;
@Override
public Pair<String, Double> collect() {
// 第一次采样必须初始化
long[][] prevTicks = CPU.getProcessorCpuLoadTicks();
try {
Thread.sleep(INTERVAL_MILLIS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// 第二次采样并计算
double[] load = CPU.getProcessorCpuLoadBetweenTicks(prevTicks);
double totalUsage = calculateTaskManagerStyle(load);
return new Pair(getName(), totalUsage);
}
/**
* Calculate the total utilization rate according to the logic of the task manager
*
* @param perCoreLoad Utilization array for each logical core (0.0-1.0)
* @return Total utilization percentage
*/
private static double calculateTaskManagerStyle(double[] perCoreLoad) {
double sum = 0.0;
for (double core : perCoreLoad) {
sum += core;
}
// Key formula: Total utilization rate=Sum of utilization rates of each core/Number of logical cores * 100
return (sum / Runtime.getRuntime().availableProcessors()) * 100;
}
@Override
public String getName() {
return "system.cpu.usage";
}
}
/**
* MemoryTotalCollector
*/
private static class MemoryTotalCollector implements DynamicCollector {
@Override
public Pair<String, Double> collect() {
return new Pair<>(getName(), MathUtils.convertBytesToGigabytes(MEMORY.getTotal()));
}
@Override
public String getName() {
return "system.memory.total";
}
}
/**
* MemoryAvailableCollector
*/
private static class MemoryAvailableCollector implements DynamicCollector {
@Override
public Pair<String, Double> collect() {
return new Pair(getName(), MathUtils.convertBytesToGigabytes(MEMORY.getAvailable()));
}
@Override
public String getName() {
return "system.memory.available";
}
}
/**
* MemoryUsageCollector
*/
private static class MemoryUsageCollector implements DynamicCollector {
@Override
public Pair<String, Double> collect() {
return new Pair(getName(),
MathUtils.calculateUtilizationPercentage(MEMORY.getAvailable(), MEMORY.getTotal()));
}
@Override
public String getName() {
return "system.memory.usage";
}
}
}

View File

@ -0,0 +1,139 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.service.task.core;
import lombok.Getter;
import org.opengauss.agent.config.AppConfig;
import org.opengauss.agent.entity.TaskDefinition;
import org.opengauss.agent.exception.TaskExecutionException;
import org.opengauss.agent.service.task.group.GroupKey;
import org.opengauss.agent.service.task.group.OtelTaskGroup;
import org.slf4j.Logger;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* OtelCommonExecutor
*
* @author: wangchao
* @Date: 2025/4/9 10:35
* @Description: OtelCommonExecutor
* @since 7.0.0-RC2
**/
public class OtelCommonExecutor {
private final Logger log;
private final Map<Long, GroupKey> taskIdToGroupKey = new ConcurrentHashMap<>();
@Getter
private final ConcurrentMap<GroupKey, OtelTaskGroup> targetGroups = new ConcurrentHashMap<>();
private final AppConfig appConfig;
/**
* Constructor
*
* @param appConfig AppConfig
* @param log Logger
*/
public OtelCommonExecutor(AppConfig appConfig, Logger log) {
this.log = log;
this.appConfig = appConfig;
}
/**
* Initialize
*
* @param task TaskDefinition
* @throws TaskExecutionException TaskExecutionException
*/
public void initialize(TaskDefinition task) throws TaskExecutionException {
GroupKey key = new GroupKey(task);
taskIdToGroupKey.put(task.getTaskId(), key);
OtelTaskGroup otelTaskGroup = targetGroups.compute(key, (k, group) -> {
if (group == null) {
OtelTaskGroup newGroup = new OtelTaskGroup(key);
newGroup.addTask(task);
newGroup.createContext(appConfig);
return newGroup;
} else {
synchronized (this) {
if (!group.validateTaskConsistency(task)) {
throw new TaskExecutionException("Task conflicts with existing group configuration");
}
group.addTask(task);
// re-create context
group.recreateContext(appConfig);
}
return group;
}
});
log.info("Initialized task execute environment {} :tasks:{}", key, otelTaskGroup.getTaskIds());
}
/**
* Has task running
*
* @return boolean
*/
public boolean hasTaskRunning() {
return targetGroups.values().stream().anyMatch(OtelTaskGroup::hasTask);
}
/**
* Remove task
*
* @param taskId Task ID
*/
public void cancal(Long taskId) {
GroupKey groupKey = taskIdToGroupKey.remove(taskId);
if (groupKey == null) {
log.warn("Task {} not found in the executor", taskId);
return;
}
OtelTaskGroup group = targetGroups.get(groupKey);
if (group == null) {
log.warn("Group for task {} not found in the executor", taskId);
return;
}
synchronized (group) {
group.removeTask(taskId);
if (group.isEmpty()) {
group.getMeterProviderContext().shutdown();
targetGroups.remove(groupKey);
log.info("Removed group {} due to no tasks", groupKey);
} else {
try {
group.recreateContext(appConfig); // re-create the context with the remaining tasks
log.info("Recreated context for group {} after task removal", groupKey);
} catch (TaskExecutionException e) {
log.error("Failed to update context after task removal", e);
}
}
}
}
/**
* Stop
*/
public void stop() {
targetGroups.forEach((target, group) -> {
group.stopPeriodicCollection();
});
targetGroups.clear();
taskIdToGroupKey.clear();
}
}

View File

@ -0,0 +1,57 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.service.task.core;
import cn.hutool.core.io.FileUtil;
import io.quarkus.arc.Unremovable;
import jakarta.enterprise.context.ApplicationScoped;
import org.opengauss.agent.entity.TaskExecution;
import java.io.File;
import java.util.List;
/**
* TaskExecutionException
*
* @author: wangchao
* @Date: 2025/4/9 17:26
* @Description: TaskExecutionRecordService
* @since 7.0.0-RC2
**/
@Unremovable
@ApplicationScoped
public class TaskExecutionRecordService {
private static final File HISTORY = new File("history.txt");
/**
* save record to history.txt file
*
* @param record record
*/
public void save(String record) {
FileUtil.appendUtf8Lines(List.of(record), HISTORY);
}
/**
* save record to history.txt file
*
* @param record record
*/
public void save(TaskExecution record) {
FileUtil.appendUtf8Lines(List.of(record), HISTORY);
}
}

View File

@ -0,0 +1,53 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.service.task.group;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import org.opengauss.agent.entity.TaskDefinition;
import org.opengauss.agent.enums.StoragePolicy;
/**
* GroupKey
*
* @author: wangchao
* @Date: 2025/5/16 16:40
* @since 7.0.0-RC2
**/
@Getter
@EqualsAndHashCode
public class GroupKey {
private final String dataSendTarget;
private final long period;
private final StoragePolicy storagePolicy;
/**
* Constructor
*
* @param task task definition
*/
public GroupKey(TaskDefinition task) {
this.dataSendTarget = task.getDataSendTarget();
this.period = task.getPeriod();
this.storagePolicy = task.getStoragePolicy();
}
@Override
public String toString() {
return "groupKey=[" + dataSendTarget + "," + period + "," + storagePolicy + "]";
}
}

View File

@ -0,0 +1,127 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.service.task.group;
import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.metrics.SdkMeterProvider;
import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.utils.URIBuilder;
import org.opengauss.agent.config.AppConfig;
import org.opengauss.agent.exception.TaskExecutionException;
import org.opengauss.agent.service.otel.HttpMetricExporter;
import org.opengauss.agent.service.task.core.MeterProviderContext;
import java.time.Duration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
/**
* OtelTaskGroup
*
* @author: wangchao
* @Date: 2025/5/16 16:44
* @since 7.0.0-RC2
**/
@Slf4j
public class OtelTaskGroup extends TaskGroup {
/**
* PARAM_TASK instrumentation scope name
*/
protected static final String INSTRUMENTATION_SCOPE_NAME = "datakit.agent.metrics";
volatile MeterProviderContext meterProviderContext;
/**
* Constructor for OtelTaskGroup
*
* @param key group key
*/
public OtelTaskGroup(GroupKey key) {
super(key);
}
/**
* getMeterProviderContext
*
* @return MeterProviderContext
*/
public MeterProviderContext getMeterProviderContext() {
return meterProviderContext;
}
/**
* createContext
*
* @param appConfig AppConfig
* @throws TaskExecutionException TaskExecutionException
*/
public void createContext(AppConfig appConfig) throws TaskExecutionException {
try {
if (meterProviderContext != null && meterProviderContext.isRegister()) {
return;
}
String httpTarget = new URIBuilder(appConfig.getAppServerUrl()).setPath(getGroupKey().getDataSendTarget())
.build()
.toString();
Map<String, Object> params = new HashMap<>();
params.put(PARAM_TASK, new ArrayList<>(getTasks().keySet()));
params.put(PARAM_AGENT, appConfig.getAgentId());
log.info("recreateContext httpTarget={},params={}", httpTarget, params);
HttpMetricExporter exporter = HttpMetricExporter.createWithParams(httpTarget, params);
PeriodicMetricReader reader = PeriodicMetricReader.builder(exporter)
.setInterval(Duration.ofMillis(getGroupKey().getPeriod()))
.build();
SdkMeterProvider meterProvider = SdkMeterProvider.builder().registerMetricReader(reader).build();
OpenTelemetrySdk sdk = OpenTelemetrySdk.builder().setMeterProvider(meterProvider).build();
Meter meter = sdk.getMeter(INSTRUMENTATION_SCOPE_NAME);
meterProviderContext = new MeterProviderContext(sdk, meterProvider, exporter, meter);
meterProviderContext.initialize(getMergedMetrics(), getMergedCollectorList(), getTaskMetricKeys());
} catch (Exception e) {
throw new TaskExecutionException("Invalid create MeterProviderContext : " + e.getMessage());
}
}
/**
* recreateContext
*
* @param appConfig AppConfig
* @throws TaskExecutionException TaskExecutionException
*/
public void recreateContext(AppConfig appConfig) throws TaskExecutionException {
try {
if (meterProviderContext != null) {
meterProviderContext.shutdown();
}
createContext(appConfig);
} catch (TaskExecutionException e) {
throw new TaskExecutionException("recreate Context Invalid target URL: " + e.getMessage());
}
}
/**
* stop periodic collection
*/
public void stopPeriodicCollection() {
super.stopPeriodicCollection();
if (meterProviderContext != null) {
meterProviderContext.shutdown();
}
}
}

View File

@ -0,0 +1,325 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.service.task.group;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Pair;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import retrofit2.Call;
import retrofit2.Response;
import org.apache.http.client.utils.URIBuilder;
import org.opengauss.agent.client.DynamicHttpClientBuilder;
import org.opengauss.agent.client.MetricHttpClient;
import org.opengauss.agent.config.AppConfig;
import org.opengauss.agent.entity.TaskDefinition;
import org.opengauss.agent.entity.task.TaskMetricsDefinitionVo;
import org.opengauss.agent.exception.TaskExecutionException;
import org.opengauss.agent.vo.MetricKey;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* TaskGroup
*
* @author: wangchao
* @Date: 2025/5/16 16:44
* @since 7.0.0-RC2
**/
@Slf4j
public class TaskGroup {
/**
* TaskGroup agentId
*/
protected static final String PARAM_AGENT = "agentId";
/**
* TaskGroup taskIds
*/
protected static final String PARAM_TASK = "taskIds";
private ScheduledExecutorService groupScheduler = Executors.newScheduledThreadPool(1);
private ScheduledFuture<?> collectionTask = null;
@Getter
private final GroupKey groupKey;
@Getter
private Map<Long, TaskDefinition> tasks = new ConcurrentHashMap<>();
private MetricHttpClient metricHttpClient;
private Map<String, Object> params = new HashMap<>();
private String httpTarget;
/**
* Constructor for OtelTaskGroup
*
* @param key group key
*/
public TaskGroup(GroupKey key) {
this.groupKey = key;
}
/**
* Create context
*
* @param appConfig appConfig
* @throws TaskExecutionException TaskExecutionException
*/
public void createContext(AppConfig appConfig) throws TaskExecutionException {
try {
if (metricHttpClient != null) {
return;
}
httpTarget = new URIBuilder(appConfig.getAppServerUrl()).setPath(groupKey.getDataSendTarget())
.build()
.toString();
metricHttpClient = DynamicHttpClientBuilder.createHttpClient(httpTarget);
params.put(PARAM_TASK, new ArrayList<>(tasks.keySet()));
params.put(PARAM_AGENT, appConfig.getAgentId());
log.info("create Context httpTarget={},params={}", httpTarget, params);
} catch (Exception e) {
throw new TaskExecutionException("Invalid create MeterProviderContext : " + e.getMessage());
}
}
/**
* Send data
*
* @param taskId taskId
* @param dataList dataList
*/
public void sendData(Long taskId, List<Map<String, Object>> dataList) {
try {
Call<Void> voidCall = metricHttpClient.sendDataMetrics(httpTarget, taskId, getAgentId(), dataList);
log.debug("Export http info | {}: {}", httpTarget, taskId);
Response<Void> response = voidCall.execute(); // 同步执行或使用enqueue异步
if (!response.isSuccessful()) {
log.error("Export failed | Code: {}", response.code());
}
} catch (IOException e) {
log.error("Export http error | {}:{}: {}", httpTarget, taskId, e.getMessage());
}
}
private Long getAgentId() {
Object object = params.get(PARAM_AGENT);
if (object == null) {
throw new TaskExecutionException("Invalid create MeterProviderContext : agentId is null");
}
if (object instanceof Long) {
return (Long) object;
} else {
throw new TaskExecutionException("Invalid create MeterProviderContext : agentId is not Long");
}
}
/**
* Refresh context
*
* @throws TaskExecutionException TaskExecutionException
*/
public void refreshContext() throws TaskExecutionException {
try {
params.remove(PARAM_TASK);
params.put(PARAM_TASK, new ArrayList<>(tasks.keySet()));
} catch (Exception e) {
throw new TaskExecutionException("Invalid create MeterProviderContext : " + e.getMessage());
}
}
/**
* Get taskIds
*
* @return Set<Long> taskIds
*/
public Set<Long> getTaskIds() {
return tasks.keySet();
}
/**
* task is empty
*
* @return boolean
*/
public boolean isEmpty() {
return !tasks.isEmpty();
}
/**
* add task
*
* @param task TaskDefinition
*/
public void addTask(TaskDefinition task) {
tasks.put(task.getTaskId(), task);
}
/**
* Remove task
*
* @param taskId taskId
*/
public void removeTask(Long taskId) {
tasks.remove(taskId);
}
/**
* Start the group task
*
* @param runnable the runnable task
*/
public void startGroupTask(Runnable runnable) {
if (this.collectionTask == null) {
log.info("Started metric collection schedule for group {} ,tasks={}", groupKey, tasks.keySet());
// 按组周期启动定时收集
this.collectionTask = groupScheduler.scheduleAtFixedRate(runnable, initialDelay(), groupKey.getPeriod(),
TimeUnit.MILLISECONDS);
}
}
/**
* half of group period as initial delay
*
* @return initial delay
*/
private long initialDelay() {
return groupKey.getPeriod() / 2;
}
/**
* check if the group has task
*
* @return boolean
*/
public boolean hasTask() {
return !tasks.isEmpty();
}
/**
* stop periodic collection
*/
public void stopPeriodicCollection() {
if (collectionTask != null) {
collectionTask.cancel(false);
}
if (groupScheduler != null) {
groupScheduler.shutdownNow();
try {
if (!groupScheduler.awaitTermination(1, TimeUnit.SECONDS)) {
groupScheduler.shutdownNow();
}
} catch (InterruptedException e) {
groupScheduler.shutdownNow();
Thread.currentThread().interrupt();
}
}
if (tasks != null) {
tasks.clear();
}
}
/**
* Get the merged collector list
*
* @return collector list
*/
public List<String> getMergedCollectorList() {
return Optional.ofNullable(tasks)
.orElseGet(Collections::emptyMap)
.values()
.stream()
.flatMap(task -> task.getCollectorList().stream())
.distinct()
.collect(Collectors.toList());
}
/**
* Get the collector list of the task
*
* @param taskId task id
* @return collector list
*/
public List<String> getCollectorList(Long taskId) {
return tasks.get(taskId).getCollectorList();
}
/**
* Get the task metric keys
*
* @return task metric keys
*/
public Map<Long, List<MetricKey>> getTaskMetricKeys() {
return tasks.entrySet()
.stream()
.filter(entry -> entry.getKey() != null && entry.getValue() != null)
.flatMap(entry -> {
final Long taskId = entry.getKey();
final List<TaskMetricsDefinitionVo> taskMetricDefs = entry.getValue().getMetricsDefinitionList();
if (CollUtil.isEmpty(taskMetricDefs)) {
return Stream.empty();
}
return taskMetricDefs.stream()
.filter(Objects::nonNull)
.map(metric -> Pair.of(taskId, MetricKey.of(metric.getName(), metric.getProp())));
})
.collect(Collectors.groupingBy(Pair::getKey, Collectors.mapping(Pair::getValue, Collectors.toList())));
}
/**
* Validate the task consistency
*
* @param newTask new task
* @return true if consistent, false otherwise
*/
public boolean validateTaskConsistency(TaskDefinition newTask) {
return Optional.ofNullable(tasks)
.orElseGet(Collections::emptyMap)
.values()
.stream()
.allMatch(existingTask -> existingTask.getPeriod() == newTask.getPeriod() && existingTask.getStoragePolicy()
.equals(newTask.getStoragePolicy()));
}
/**
* Get all metrics from all tasks
*
* @return a list of metrics
*/
public List<TaskMetricsDefinitionVo> getMergedMetrics() {
return new ArrayList<>(Optional.ofNullable(tasks)
.orElseGet(Collections::emptyMap)
.values()
.stream()
.flatMap(task -> task.getMetricsDefinitionList().stream())
.collect(Collectors.toMap(TaskMetricsDefinitionVo::getName, Function.identity(), (a, b) -> a))
.values());
}
}

View File

@ -0,0 +1,94 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.utils;
import org.opengauss.agent.constant.AgentConstants;
import java.time.Duration;
import java.time.LocalDate;
import java.time.Period;
import java.time.temporal.ChronoUnit;
/**
* DurationUtils
*
* @author: wangchao
* @Date: 2025/6/6 16:25
* @since 7.0.0-RC2
**/
public class DurationUtils {
/**
* Convert milliseconds to Duration isoString
*
* @param milliseconds milliseconds
* @return Duration isoString
*/
public static String formatInterval(long milliseconds) {
Duration duration = Duration.ofMillis(milliseconds);
String iso = duration.toString(); // 示例PT5S
return iso.substring(2); // 移除 PT 前缀 5S
}
/**
* Convert Duration isoString to milliseconds
*
* @param isoString isoString
* @return milliseconds
*/
public static long parseToMillis(String isoString) {
if (isoString.startsWith(AgentConstants.Duration.PREFIX_ONLE_TIME)) {
// 解析时间部分小时//
Duration duration = Duration.parse(isoString);
return duration.toMillis();
}
// 分离日期部分///和时间部分小时//
String[] parts = isoString.split(AgentConstants.Duration.SPLIT_DATE_AND_TIME);
String datePart = parts[0].substring(1); // 去掉前缀P
String timePart = parts.length > 1 ? parts[1] : "";
// 解析日期部分///
Period period = Period.parse(AgentConstants.Duration.PREFIX_DATE + datePart);
LocalDate start = LocalDate.now();
LocalDate end = start.plus(period);
long days = ChronoUnit.DAYS.between(start, end);
// 解析时间部分小时//
Duration duration = Duration.parse(AgentConstants.Duration.PREFIX_ONLE_TIME + timePart);
// 合并总毫秒数
return Duration.ofDays(days).plus(duration).toMillis();
}
/**
* Convert fixed time isoString to milliseconds
*
* @param isoString isoString
* @return milliseconds
*/
public static long parseFixedTimeToMillis(String isoString) {
// 分离日期部分///和时间部分小时//
String[] parts = isoString.split(AgentConstants.Duration.SPLIT_DATE_AND_TIME);
String datePart = parts[0];
if (datePart.contains(AgentConstants.Duration.YEAR) || datePart.contains(AgentConstants.Duration.MONTH)) {
throw new IllegalArgumentException("fixed time period must be days");
}
String timePart = parts.length > 1 ? parts[1] : "";
// 解析 /
Period period = Period.parse(datePart);
long days = period.getDays();
// 解析时间部分小时//
Duration duration = Duration.parse(AgentConstants.Duration.PREFIX_ONLE_TIME + timePart);
// 合并总毫秒数
return Duration.ofDays(days).plus(duration).toMillis();
}
}

View File

@ -0,0 +1,85 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.utils;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
/**
* MathUtils
*
* @author: wangchao
* @Date: 2025/6/6 16:25
* @since 7.0.0-RC2
**/
public class MathUtils {
private static final long LONG_1024_1024_1024 = 1024 * 1024 * 1024;
private static final BigDecimal DECIMAL_1024_1024_1024 = BigDecimal.valueOf(LONG_1024_1024_1024);
private static final BigDecimal DECIMAL_1024 = BigDecimal.valueOf(1024);
private static final BigDecimal HUNDRED = BigDecimal.valueOf(100);
private static final MathContext DIVISION_CONTEXT = new MathContext(6, RoundingMode.HALF_UP); // 预计算精度
/**
* calc double divide 1024*1024*1024
*
* @param dividend dividend
* @return double
*/
public static double convertBytesToGigabytes(long dividend) {
return new BigDecimal(dividend).divide(DECIMAL_1024_1024_1024, DIVISION_CONTEXT).doubleValue();
}
/**
* calc double divide 1024
*
* @param dividend dividend
* @return double
*/
public static double divide1024(long dividend) {
return new BigDecimal(dividend).divide(DECIMAL_1024, DIVISION_CONTEXT).doubleValue();
}
/**
* usage percentage percentage = (1.0 - available / total) * 100.0;
*
* @param available available
* @param total total
* @return double usage percentage
*/
public static double calculateUtilizationPercentage(long available, long total) {
if (total == 0 || available >= total) {
return 0.0;
}
BigDecimal used = BigDecimal.valueOf(total - available);
BigDecimal totalValue = BigDecimal.valueOf(total);
return used.multiply(HUNDRED).divide(totalValue, 4, RoundingMode.HALF_UP).doubleValue();
}
/**
* safe parse double value to double, if parse failed, return Double.NaN
*
* @param value value
* @return double
*/
public static Double doubleValueOf(String value) {
try {
return Double.valueOf(value);
} catch (NumberFormatException e) {
return Double.NaN;
}
}
}

View File

@ -0,0 +1,118 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.utils;
import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.ExecuteException;
import org.apache.commons.exec.ExecuteWatchdog;
import org.apache.commons.exec.PumpStreamHandler;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.opengauss.agent.entity.OsCmdResult;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;
/**
* OsCommandUtils
*
* @author: wangchao
* @Date: 2025/7/7 09:47
* @since 7.0.0-RC2
**/
public class OsCommandUtils {
private static final Set<String> ALLOWED_COMMANDS = ConcurrentHashMap.newKeySet();
private static final Pattern SAFE_ARG_PATTERN = Pattern.compile("^[a-zA-Z0-9\\s\\-\\.]+$");
/**
* force refresh allowed command Collection
*
* @param allowedCommands allowed command Collection
*/
public static void forceRefreshAllowedCommand(List<String> allowedCommands) {
ALLOWED_COMMANDS.addAll(allowedCommands);
}
/**
* get allowed command Collection
*
* @return allowed command Collection
*/
public static Set<String> getAllowedCommands() {
return ALLOWED_COMMANDS;
}
/**
* os cmd execute
*
* @param commandLine os cmd
* @return OsCmdResult
*/
public static OsCmdResult execute(String commandLine) {
if (!isSafeCommand(commandLine)) {
return new OsCmdResult(-1, "Unsafe command detected: " + commandLine);
}
CommandLine cmdLine = CommandLine.parse(commandLine);
DefaultExecutor executor = new DefaultExecutor();
executor.setExitValues(null);
executor.setWatchdog(new ExecuteWatchdog(60000));
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
PumpStreamHandler streamHandler = new PumpStreamHandler(outputStream);
executor.setStreamHandler(streamHandler);
try {
return new OsCmdResult(executor.execute(cmdLine), outputStream.toString(Charset.defaultCharset()));
} catch (ExecuteException e) {
return new OsCmdResult(e.getExitValue(),
"Execution failed: " + outputStream.toString(Charset.defaultCharset()));
} catch (IOException e) {
return new OsCmdResult(-1, "Cmd Error: " + e.getMessage());
} finally {
try {
outputStream.close();
} catch (IOException ignored) {
}
}
}
/**
* check command is safe
*
* @param commandLine commandLine
* @return isSafeCommand
*/
protected static boolean isSafeCommand(String commandLine) {
if (commandLine == null || commandLine.trim().isEmpty()) {
return false;
}
String[] parts = commandLine.trim().split("\\s+", 2);
if (parts.length == 0) {
return false;
}
String commandName = parts[0].contains("/") ? parts[0].substring(parts[0].lastIndexOf('/') + 1) : parts[0];
if (!ALLOWED_COMMANDS.contains(commandName)) {
return true;
}
if (parts.length > 1) {
String args = parts[1];
return SAFE_ARG_PATTERN.matcher(args).matches();
}
return true;
}
}

View File

@ -0,0 +1,123 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.utils;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.CryptoException;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.opengauss.agent.exception.AgentException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.atomic.AtomicReference;
/**
* RsaUtils
*
* @author wangchao
* @since 2024/10/29 09:26
*/
@Slf4j
public class RsaUtils {
private static final AtomicReference<String> PUBLIC_KEY_CACHE = new AtomicReference<>();
private static final AtomicReference<String> PRIVATE_KEY_CACHE = new AtomicReference<>();
static {
initRsaKeyPair();
}
private static void initRsaKeyPair() {
try {
KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA");
gen.initialize(4096);
KeyPair keyPair = gen.generateKeyPair();
PUBLIC_KEY_CACHE.set(Base64.encodeBase64String(keyPair.getPublic().getEncoded()));
PRIVATE_KEY_CACHE.set(Base64.encodeBase64String(keyPair.getPrivate().getEncoded()));
} catch (NoSuchAlgorithmException e) {
log.error("RSA key pair generation failed", e);
throw new AgentException("Critical failure: RSA key generation failed", e);
}
}
/**
* get public key
*
* @return public key
*/
public static String publicKey() {
if (StrUtil.isEmpty(PUBLIC_KEY_CACHE.get())) {
initRsaKeyPair();
}
return PUBLIC_KEY_CACHE.get();
}
/**
* encrypt
*
* @param plainText plain text
* @return cipher text
*/
public static String encrypt(String plainText) {
return encrypt(plainText, PUBLIC_KEY_CACHE.get());
}
/**
* encrypt
*
* @param plainText plain text
* @param publicKey public key
* @return cipher text
*/
public static String encrypt(String plainText, String publicKey) {
RSA rsa = new RSA(null, publicKey);
byte[] encrypt = rsa.encrypt(StrUtil.bytes(plainText, CharsetUtil.CHARSET_UTF_8), KeyType.PublicKey);
return Base64.encodeBase64String(encrypt);
}
/**
* decrypt
*
* @param cipherText cipher text
* @return plain text
*/
public static String decrypt(String cipherText) {
return decrypt(cipherText, PRIVATE_KEY_CACHE.get());
}
/**
* decrypt
*
* @param cipherText cipher text
* @param privateKey private key
* @return plain text
*/
public static String decrypt(String cipherText, String privateKey) {
try {
RSA rsa = new RSA(privateKey, null);
byte[] decrypt = rsa.decrypt(Base64.decodeBase64(cipherText), KeyType.PrivateKey);
return StrUtil.str(decrypt, CharsetUtil.CHARSET_UTF_8);
} catch (CryptoException e) {
log.error("RSA decryption failed for cipherText", e);
throw new SecurityException("Decryption failed. Possible tampering detected", e);
}
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.vo;
import io.opentelemetry.api.common.Attributes;
import lombok.Data;
import java.util.concurrent.atomic.AtomicReference;
/**
* MetricData
*
* @author: wangchao
* @Date: 2025/5/6 09:55
* @Description: MetricData
* @since 7.0.0-RC2
**/
@Data
public class MetricData {
private AtomicReference<Double> value = new AtomicReference<>(0.0);
private Attributes attributes = Attributes.empty();
}

View File

@ -0,0 +1,64 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.vo;
import lombok.Data;
/**
* MetricKey
*
* @author: wangchao
* @Date: 2025/5/6 09:53
* @Description: MetricKey
* @since 7.0.0-RC2
**/
@Data
public class MetricKey {
private final String metricName;
private final String propValue;
/**
* Constructor
*
* @param metricName metric name
* @param propValue prop value
*/
private MetricKey(String metricName, String propValue) {
this.metricName = metricName;
this.propValue = propValue;
}
/**
* Create MetricKey
*
* @param metricName metric name
* @param propValue prop value
* @return MetricKey
*/
public static MetricKey of(String metricName, String propValue) {
return new MetricKey(metricName, propValue);
}
/**
* Create MetricKey
*
* @param metricName metric name
* @return MetricKey
*/
public static MetricKey of(String metricName) {
return new MetricKey(metricName, null);
}
}

View File

@ -0,0 +1,46 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.vo;
import cn.hutool.core.lang.Pair;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* MultiValueMetric
*
* @author: wangchao
* @Date: 2025/5/13 20:21
* @since 7.0.0-RC2
**/
@Data
@AllArgsConstructor
public class MultiValueMetric {
private String name;
private Pair<String, Double> propValue;
/**
* Create MultiValueMetric
*
* @param name metric name
* @param propName prop name
* @param propValue prop value
* @return MultiValueMetric
*/
public static MultiValueMetric of(String name, String propName, Double propValue) {
return new MultiValueMetric(name, Pair.of(propName, propValue));
}
}

View File

@ -0,0 +1,47 @@
agent:
id: 1
version: 7.0.0-RC3
server: http://127.0.0.1:9494
name: datakit-agent
heartbeat:
interval: 2
break-wait-max-times: 3
thread-pool:
max-threads: 10
os:
command-write-list: df,free,top,ps,netstat
quarkus:
http:
port: 19001
scheduler:
start-mode: forced
thread-pool:
core-threads: 1
max-threads: 10
keep-alive-time: 10000
rest-client:
"agent.server":
uri: ${agent.server}
connect-timeout: 5000
read-timeout: 10000
log:
category:
"org.apache.http":
level: WARN
"io.vertx.core.impl.EventLoopContext":
level: WARN
"io.netty":
level: ERROR
file:
level: DEBUG
enable: true
path: /path/datakit_agent.log
max-size: 100MB
max-history: 30
format: "%d{yyyy-MM-dd HH:mm:ss.SSS} %-5p [%c{1.}] (%t) %s%e%n"
pattern: logs/datakit_agent.%d{yyyy-MM-dd}.log
console:
enable: true
level: DEBUG
color: true
format: "%d{yyyy-MM-dd HH:mm:ss.SSS} %-5p [%c{1.}] (%t) %s%e%n"

View File

@ -0,0 +1,38 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.duration;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
import org.opengauss.agent.utils.DurationUtils;
/**
* DurationStringTest
*
* @author: wangchao
* @Date: 2025/4/3 16:23
* @Description: DurationStringTest
* @since 7.0.0-RC2
**/
public class DurationStringTest {
@Test
void testDurationToString() {
assertEquals("1S", DurationUtils.formatInterval(1000));
assertEquals("1.2S", DurationUtils.formatInterval(1200));
assertEquals("0.2S", DurationUtils.formatInterval(200));
}
}

View File

@ -0,0 +1,115 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.duration;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
import org.opengauss.agent.utils.DurationUtils;
import java.time.Duration;
import java.time.Period;
/**
* DurationTest
*
* @author: wangchao
* @Date: 2025/4/16 10:43
* @Description: DurationTest
* @since 7.0.0-RC2
**/
public class DurationTest {
@Test
public void test1() {
Duration p0 = Duration.parse("PT-1s");
assertEquals(-1, p0.toSeconds());
}
@Test
public void test2() {
Duration p0 = Duration.parse("PT0s");
assertEquals(0, p0.toSeconds());
}
@Test
public void test3() {
Duration p0 = Duration.parse("P1D");
assertEquals(86400, p0.toSeconds());
}
@Test
public void test4() {
Period p0 = Period.parse("P2W");
long seconds = Duration.ofDays(p0.getDays()).toSeconds();
assertEquals(1209600, seconds);
}
@Test
public void test5() {
Period p0 = Period.parse("P3M2D");
assertEquals(3, p0.getMonths());
assertEquals(2, p0.getDays());
}
@Test
public void test6() {
Duration p0 = Duration.parse("PT0.01s");
assertEquals(10, p0.toMillis());
}
@Test
public void test7() {
Duration parse = Duration.parse("PT10s");
assertEquals(10000, parse.toMillis());
}
@Test
public void test8() {
Duration parse2 = Duration.parse("PT2M10s");
assertEquals(130000, parse2.toMillis());
}
@Test
public void test9() {
Duration parse3 = Duration.parse("PT1H2M10s");
assertEquals(3730000, parse3.toMillis());
}
@Test
public void test10() {
Duration parse4 = Duration.parse("P1D");
assertEquals(86400000, parse4.toMillis());
}
@Test
public void test11() {
Duration parse5 = Duration.parse("P100D");
assertEquals(8640000000L, parse5.toMillis());
}
@Test
public void test12() {
long parse6 = DurationUtils.parseFixedTimeToMillis("P3W4DT5H6M7.5S"); // 固定时间周期 3周4天5小时6分7.5秒
assertEquals(2178367500L, parse6);
}
@Test
public void test13() {
String input = "P1Y2M3W4DT5H6M7.5S"; // 1年2月3周4天5小时6分7.5秒
long totalMs = DurationUtils.parseToMillis(input);
assertEquals(38984767500L, totalMs);
}
}

View File

@ -0,0 +1,79 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.utils;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* MathUtilsTest
*
* @author: wangchao
* @Date: 2025/7/3 16:12
* @since 7.0.0-RC2
**/
public class MathUtilsTest {
/**
* Test convertBytesToGigabytes method.
*/
@Test
public void testConvertBytesToGigabytes() {
// Test normal cases
assertEquals(1.0, MathUtils.convertBytesToGigabytes(1024L * 1024 * 1024), 0.0001);
assertEquals(1.5, MathUtils.convertBytesToGigabytes(1024L * 1024 * 1024 * 3 / 2), 0.0001);
// Test boundary cases
assertEquals(0.0, MathUtils.convertBytesToGigabytes(0), 0.0001);
assertEquals(9.31323E-10, MathUtils.convertBytesToGigabytes(1), 0.0001);
// Test large value
assertEquals(1000.0, MathUtils.convertBytesToGigabytes(1024L * 1024 * 1024 * 1000), 0.0001);
}
/**
* Test divide1024 method.
*/
@Test
public void testDivide1024() {
// Test normal cases
assertEquals(1.0, MathUtils.divide1024(1024), 0.0001);
assertEquals(1.5, MathUtils.divide1024(1536), 0.0001);
// Test boundary cases
assertEquals(0.0, MathUtils.divide1024(0), 0.0001);
assertEquals(0.000976562, MathUtils.divide1024(1), 0.0001);
// Test large value
assertEquals(1000.0, MathUtils.divide1024(1024 * 1000), 0.0001);
}
/**
* Test calculateUtilizationPercentage method.
*/
@Test
public void testCalculateUtilizationPercentage() {
// Test normal cases
assertEquals(50.0, MathUtils.calculateUtilizationPercentage(500, 1000), 0.0001);
assertEquals(33.4, MathUtils.calculateUtilizationPercentage(666, 1000), 0.0001);
// Test boundary cases
// total = 0
assertEquals(0.0, MathUtils.calculateUtilizationPercentage(0, 0), 0.0001);
// available = 0
assertEquals(100.0, MathUtils.calculateUtilizationPercentage(0, 1000), 0.0001);
// available = total
assertEquals(0.0, MathUtils.calculateUtilizationPercentage(1000, 1000), 0.0001);
// Test edge cases
assertEquals(99.9999, MathUtils.calculateUtilizationPercentage(1, 1000000), 0.0001);
assertEquals(0.0001, MathUtils.calculateUtilizationPercentage(999999, 1000000), 0.0001);
}
}

View File

@ -0,0 +1,54 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITFOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.opengauss.agent.utils;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
import org.junit.platform.commons.util.ReflectionUtils;
import java.util.List;
/**
* OsCommandUtilsTest
*
* @author: wangchao
* @Date: 2025/7/7 10:16
* @since 7.0.0-RC2
**/
public class OsCommandUtilsTest {
@Test
public void testAllowedCommandReflection() {
String command = "echo hello";
ReflectionUtils.findMethod(OsCommandUtils.class, "isSafeCommand", String.class).ifPresent(method -> {
ReflectionUtils.makeAccessible(method);
assertEquals(true, ReflectionUtils.invokeMethod(method, OsCommandUtils.class, command));
});
}
@Test
public void testAllowedCommandProtected() {
String command = "echo hello";
assertEquals(true, OsCommandUtils.isSafeCommand(command));
}
@Test
public void testForceRefreshAllowedCommand() {
String command = "msg ax";
OsCommandUtils.forceRefreshAllowedCommand(List.of(command));
assertEquals(true, OsCommandUtils.isSafeCommand(command));
}
}

44
ER-Model/openGauss-datakit/.gitignore vendored Normal file
View File

@ -0,0 +1,44 @@
######################################################################
# Build Tools
**/.DS_Store
.gradle
/build/
!gradle/wrapper/gradle-wrapper.jar
target/
!.mvn/wrapper/maven-wrapper.jar
######################################################################
# IDE
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
nbproject/private/
build/*
nbbuild/
dist/
nbdist/
.nb-gradle/
######################################################################
# Others
*.log
*.xml.versionsBackup
*.swp
!*/build/*.java
!*/build/*.html
!*/build/*.xml

View File

@ -0,0 +1,180 @@
## 代码结构说明
```
├── visualtool-api //服务端API模块
│   ├── pom.xml
│   ├── com.xx.ops //运维业务包
│   ├── com.xx.modeling //数据建模业务包
├── visualtool-common //公共工具模块
│   ├── pom.xml
│   ├── src
├── visualtool-framework //集成框架模块
│   ├── pom.xml
│   ├── src
├── visualtool-service //系统业务代码模块(service、mapper、domain、xml)
│   ├── pom.xml
│   ├── com.xx.xx.ops //运维业务包
│   ├── com.xx.xx.modeling //数据建模业务包
├── visualtool-ui //前端模块
```
## 数据库对象说明
| 名称 | 类型 | 说明 |
|----------------|-----|---------------|
| sq_sys_log_config_id | 序列 | 系统日志配置表自增序列 |
| sq_sys_menu_id | 序列 | 系统菜单表自增序列 |
| sq_sys_log_id | 序列 | 系统操作日志表自增序列 |
| sq_sys_plugin_config_data_id | 序列 | 系统插件配置数据表自增序列 |
| sq_sys_plugin_config_id | 序列 | 系统插件配置表自增序列 |
| sq_sys_plugin_id | 序列 | 系统插件表自增序列 |
| sq_sys_role_id | 序列 | 系统角色表自增序列 |
| sq_sys_user_id | 序列 | 系统用户表自增序列 |
| sq_sys_white_list_id | 序列 | 系统安全白名单表自增序列 |
| sys_log_config | 表 | 系统日志配置表 |
| sys_menu | 表 | 系统菜单表 |
| sys_oper_log | 表 | 系统操作日志表 |
| sys_plugin_config | 表 | 系统插件配置表 |
| sys_plugin_config_data | 表 | 系统插件配置数据表 |
| sys_plugins | 表 | 系统插件表 |
| sys_role | 表 | 系统角色表 |
| sys_role_menu | 表 | 系统角色与菜单关系表 |
| sys_user | 表 | 系统用户表 |
| sys_user_role | 表 | 系统用户与角色关系表 |
| sys_white_list | 表 | 系统安全白名单表 |
| ops_az | 表 | 系统可用区信息表 |
| ops_backup | 表 | 系统备份信息表 |
| ops_check | 表 | 系统一键自检信息表 |
| ops_cluster | 表 | 系统集群信息表 |
| ops_cluster_node | 表 | 系统集群节点信息表 |
| ops_encryption | 表 | 系统密钥信息表 |
| ops_host | 表 | 系统主机信息表 |
| ops_host_user | 表 | 系统主机用户信息表 |
| ops_jdbcdb_cluster | 表 | 系统数据库资源集群表 |
| ops_jdbcdb_cluster_node | 表 | 系统数据库资源集群节点表 |
| ops_package_manager | 表 | 系统安装包管理表 |
| ops_wdr | 表 | 系统WDR表 |
## 支持的服务器系统
openEuler 20.3LTSx86_x64ARMcentos7.xx86_x64
## 安装部署
### jar包安装
```shell
#1、创建datakit工作目录并在工作目录中创建存放系统运行数据的子目录
mkdir -p 自定义工作目录
cd 自定义工作目录
mkdir -p logs config ssl files
#2、将visualtool-main.jar包上传至 $自定义工作目录 下
#3、修改application-temp.yml文件中的数据链链接ip、port、database、dbuser、dbpassword。将修改后的配置文件application-temp.yml传至 $自定义工作目录/config/下
#4、修改配置文件中平台工作目录
sed -i s#/ops#$(pwd)#g config/application-temp.yml
#4、创建ssl文件
keytool -genkey -noprompt \
-dname "CN=opengauss, OU=opengauss, O=opengauss, L=Beijing, S=Beijing, C=CN"\
-alias opengauss\
-storetype PKCS12 \
-keyalg RSA \
-keysize 2048 \
-keystore $(pwd)/ssl/keystore.p12 \
-validity 3650 \
-storepass 123456
#5、执行启动命令
nohup java -Xms2048m -Xmx4096m -jar $(pwd)/visualtool-main.jar \
--spring.profiles.active=temp >$(pwd)/logs/visualtool-main.out 2>&1 &
```
### docker安装
```shell
#1、创建datakit工作目录并在工作目录中创建存放系统运行数据的子目录
mkdir -p 自定义工作目录
cd 自定义工作目录
mkdir -p logs config ssl files
#2、将visualtool-main.jar包上传至 $自定义工作目录 下
#3、修改application-temp.yml文件中的数据链链接ip、port、database、dbuser、dbpassword。将修改后的配置文件application-temp.yml传至 $自定义工作目录/config/下
#4、创建ssl文件
keytool -genkey -noprompt \
-dname "CN=opengauss, OU=opengauss, O=opengauss, L=Beijing, S=Beijing, C=CN"\
-alias opengauss\
-storetype PKCS12 \
-keyalg RSA \
-keysize 2048 \
-keystore $(pwd)/ssl/keystore.p12 \
-validity 3650 \
-storepass 123456
#5、编辑Dockerfile
vi Dockerfile
#复制以下内容进行粘贴
FROM openjdk:11
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
EXPOSE 9494 5432
WORKDIR /ops/
ENTRYPOINT ["java","-Xms2048m","-Xmx4096m", "-jar", "visualtool-main.jar"]
#6、构建镜像
docker build -f Dockerfile -t datakit:5.0.0 .
#7、启动容器
docker run -idt -p 9494:9494 \
-v /etc/localtime:/etc/localtime:ro \
-v $(pwd):/ops \
--name datakit datakit:5.0.0 \
--spring.profiles.active=temp
```
## 升级步骤
### jar包升级
```shell
#1、将新的jar包传至 自定义工作目录 下替换原有visualtool-main.jar
cd 自定义工作目录
#2、关闭原有进程后,再执行启动命令
nohup java -Xms2048m -Xmx4096m -jar $(pwd)/visualtool-main.jar \
--spring.profiles.active=temp >$(pwd)/logs/visualtool-main.out 2>&1 &
```
### docker安装升级
```shell
#1、将新的jar包传至 自定义工作目录 下替换原有visualtool-main.jar
#2、重启容器
docker restart datakit
```
### 平台配置修改
#### 端口修改
在工作目录的config目录中修改配置文件将默认9494端口修改为自定义端口之后重启服务
### 注意事项:
1、当前平台运行依赖于openJdk11。
2、平台使用的数据库当前仅支持openGauss数据库并且需要提前创建database。
3、需要将部署服务器IP配置在平台使用的数据库openGauss的白名单列表中。
4、平台默认的登录账号密码admin/admin123请在首次登录后及时修改密码。
## 卸载步骤
1、停止java进程或者docker容器删除工作目录。
2、手动清理数据库中所有的表和序列。
## 后端说明
> 1、后端返回给前端的响应编码统一在org.opengauss.admin.common.enums.ResponseCode中定义按照规则划分模块规则如下
+ 501xx 一体化模块比如50101、50102
## 前端说明
## 主程序接口暴露
为了更好的通过插件扩展系统功能,主程序需要暴露接口给插件扩展和使用。主要有两类接口需要暴露:
+ 给插件扩展的Interface
> 在visualtool-service模块的org.opengauss.admin.system.plugin.extract包里定义。
+ 给插件调用的Service
> 在visualtool-service模块的org.opengauss.admin.system.plugin.facade包里定义。
## 相关文档
[openGauss安装文档]( https://docs.opengauss.org/zh/docs/3.0.0/docs/installation/%E5%8D%95%E8%8A%82%E7%82%B9%E5%AE%89%E8%A3%85.html )
[主程序开放接口文档]( https://fullstack-dao.feishu.cn/docx/doxcnIa9e0ChR4bJWlx4IyBfzjf )
[主程序前端开发规范指导]( https://fullstack-dao.feishu.cn/docx/doxcnyE9BNt2mm0WV5o2dPqxAec )
[插件开发手册]( https://fullstack-dao.feishu.cn/docx/doxcnu2EjetnyXmL1sYIooyrivp )
[主程序扩展手册]( https://fullstack-dao.feishu.cn/docx/doxcnV63pz1w4bn4Zn1y2lxwJnf )

View File

@ -0,0 +1,259 @@
spring-boot-starter-web:
groupId: org.springframework.boot
artifactId: spring-boot-starter-web
version: 2.5.6
url: https://github.com/spring-projects/spring-boot
spring-brick:
groupId: com.gitee.starblues
artifactId: spring-brick
version: 3.1.0
url: https://gitee.com/starblues/springboot-plugin-framework-parent/tree/master/spring-brick-bootstrap
spring-brick-bootstrap:
groupId: com.gitee.starblues
artifactId: spring-brick-bootstrap
version: 3.1.0
url: https://gitee.com/starblues/springboot-plugin-framework-parent/tree/master/spring-brick-bootstrap
lombok:
groupId: org.projectlombok
artifactId: lombok
version: 1.18.24
url: http://github.com/projectlombok/lombok
druid-spring-boot-starter:
groupId: com.alibaba
artifactId: druid-spring-boot-starter
version: 1.2.6
url: https://wenshao@github.com/alibaba/druid.git
fastjson:
groupId: com.alibaba
artifactId: fastjson
version: 1.2.78
url: https://github.com/alibaba/fastjson2
mybatis-plus-boot-starter:
groupId: com.baomidou
artifactId: mybatis-plus-boot-starter
version: 3.5.0
url: https://github.com/baomidou/mybatis-plus
baomidou:
groupId: com.baomidou
artifactId: mybatis-plus-boot-starter
version: 3.5.0
url: https://github.com/baomidou/mybatis-plus
jsch:
groupId: com.github.mwiede
artifactId: jsch
version: 0.2.9
url: https://github.com/mwiede/jsch
javax.servlet-api:
groupId: javax.servlet
artifactId: javax.servlet-api
version: 4.0.1
url: https://github.com/javaee/servlet-spec
hutool-all:
groupId: cn.hutool
artifactId: hutool-all
version: 5.3.5
url: https://github:com/looly/hutool
junit:
groupId: junit
artifactId: junit
version: 4.13
url: https://github.com/junit-team/junit4
opengauss-jdbc:
groupId: org.opengauss
artifactId: opengauss-jdbc
version: 3.0.0
url: https://gitee.com/opengauss/openGauss-connector-jdbc
knife4j-spring-boot-starter:
groupId: com.github.xiaoymin
artifactId: knife4j-spring-boot-starter
version: 3.0.3
url: https://gitee.com/xiaoym/knife4j
javax.annotation-api:
groupId: javax.annotation
artifactId: javax.annotation-api
version: 1.3.2
url: http://jcp.org/en/jsr/detail?id=250
istack-commons-runtime:
groupId: com.sun.istack
artifactId: istack-commons-runtime
version: 3.0.11
url: https://github.com/eclipse-ee4j/jaxb-istack-commons
FastInfoset:
groupId: com.sun.istack
artifactId: FastInfoset
version: 1.2.18
url: https://projects.eclipse.org/projects/ee4j.jaxb-impl
jaxb-api:
groupId: javax.xml.bind
artifactId: jaxb-api
version: 2.4.0-b180725.0427
url: https://github.com/javaee/jaxb-spec
jaxb-runtime:
groupId: org.glassfish.jaxb
artifactId: jaxb-runtime
version: 2.4.0-b180725.0644
url: http://jaxb.java.net
commons-lang3:
groupId: org.apache.commons
artifactId: commons-lang3
version: 3.12.0
url: https://commons.apache.org/proper/commons-lang/
commons-io:
groupId: commons-io
artifactId: commons-io
version: 2.14.0
url: https://commons.apache.org/proper/commons-io/
commons-fileupload:
groupId: commons-fileupload
artifactId: commons-fileupload
version: 1.4
url: http://commons.apache.org/proper/commons-fileupload/
snakeyaml:
groupId: org.yaml
artifactId: snakeyaml
version: 1.28
url: http://www.snakeyaml.org
jjwt:
groupId: io.jsonwebtoken
artifactId: jjwt
version: 0.9.1
url: https://github.com/jwtk/jjwt
guava:
groupId: com.google.guava
artifactId: guava
version: 20.0
url: https://github.com/google/guava/
commons-pool2:
groupId: org.apache.commons
artifactId: commons-pool2
version: 2.9.0
url: https://commons.apache.org/proper/commons-pool/
commons-codec:
groupId: commons-codec
artifactId: commons-codec
version: 1.15
url: https://commons.apache.org/proper/commons-codec/
easyexcel:
groupId: com.alibaba
artifactId: easyexcel
version: 3.1.1
url: https://github.com/alibaba/easyexcel/easyexcel
spring-boot-starter-websocket:
groupId: org.springframework.boot
artifactId: spring-boot-starter-websocket
version: 2.5.6
url: https://github.com/spring-projects/spring-boot
oshi-core:
groupId: com.github.oshi
artifactId: oshi-core
version: 5.8.0
url: https://github.com/oshi/oshi.git
spring-boot-starter-actuator:
groupId: org.springframework.boot
artifactId: spring-boot-starter-actuator
version: 2.5.6
url: https://github.com/spring-projects/spring-boot
micrometer-registry-prometheus:
groupId: io.micrometer
artifactId: micrometer-registry-prometheus
version: 1.7.5
url: https://github.com/micrometer-metrics/micrometer
antv/x6:
cpeName: antv/x6
version: 1.32.10
url: https://github.com/antvis/X6
antv/x6-vue-shapeantv/x6:
cpeName: antv/x6-vue-shape
version: 1.4.2
url: https://github.com/antvis/X6/pkgs/npm/x6-vue-shape
arco-design/web-vue:
cpeName: arco-design/web-vue
version: 2.37.3
url: https://github:com/arco-design/arco-design-vue
highlightjs/vue-plugin:
cpeName: highlightjs/vue-plugin
version: 2.1.0
url: https://github.com/highlightjs/vue-plugin
vueuse/core:
cpeName: vueuse/core
version: 7.3.0
url: https://github.com/vueuse/vueuse
animate.css:
cpeName: animate.css
version: 4.1.1
url: https://github.com/animate-css/animate.css
axios:
cpeName: axios
version: 0.27.2
url: https://github.com/axios/axios
core-js:
cpeName: core-js
version: 3.8.3
url: https://github.com/zloirock/core-js
dayjs:
cpeName: dayjs
version: 1.11.4
url: https://github.com/iamkun/dayjs/
echarts:
cpeName: echarts
version: 5.2.2
url: https://github.com/apache/echarts
highlight.js:
cpeName: highlight.js
version: 11.6.0
url: https://github.com/highlightjs/highlight.js/
mitt:
cpeName: mitt
version: 3.0.0
url: https://github.com/developit/mitt
nprogress:
cpeName: nprogress
version: 0.2.0
url: https://github.com/rstacruz/nprogress
pinia:
cpeName: pinia
version: 2.0.17
url: https://github.com/vuejs/pinia
query-string:
cpeName: query-string
version: 7.1.1
url: https://github.com/sindresorhus/query-string
jsencrypt:
cpeName: jsencrypt
version: 3.0.0-rc.1
url: https://github.com/travist/jsencrypt
vue:
cpeName: vue
version: 3.2.13
url: https://github.com/vuejs/vue
vue-echarts:
cpeName: vue-echarts
version: 6.2.3
url: https://github.com/ecomfe/vue-echarts
vue-grid-layout:
cpeName: vue-grid-layout
version: 3.0.0-beta1
url: https://github.com/jbaysolutions/vue-grid-layout
vue-i18n:
cpeName: vue-i18n
version: 9.3.0-beta.6
url: https://github.com/intlify/vue-i18n-next
vue-router:
cpeName: vue-router
version: 4.0.3
url: https://github.com/vuejs/router
vue-winbox:
cpeName: vue-winbox
version: 0.1.0
url: https://github.com/wobsoriano/vue-winbox
wujie-vue3:
cpeName: wujie-vue3
version: 1.0.0
url: https://github.com/Tencent/wujie
xterm:
cpeName: xterm
version: 4.19.0
url: https://github.com/xtermjs/xterm.js

View File

@ -0,0 +1,50 @@
system:
# File storage path
defaultStoragePath: /ops/files
# Whitelist control switch
whitelist:
enabled: false
server:
port: 9494
ssl:
key-store: /ops/ssl/keystore.p12
key-store-password: '******'
key-store-type: PKCS12
enabled: true
servlet:
context-path: /
logging:
file:
path: /ops/logs/
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
# For openGauss
# driver-class-name: org.opengauss.Driver
# url: jdbc:opengauss://ip:port/database?currentSchema=public&batchMode=off
# username: dbuser
# password: dbpassword
druid:
test-while-idle: true
test-on-borrow: true
validation-query: "select 1"
validation-query-timeout: 10000
connection-error-retry-attempts: 0
break-after-acquire-failure: true
max-wait: 300000
keep-alive: true
max-active: 30
min-evictable-idle-time-millis: 600000
management:
server:
port: 9494
plugins:
oauth-login:
# A unique identifier obtained by registering with DevKit
client-id: your_client_id
# A secret key paired with the client_id
client-secret: your_client_secret
datakit-url: https://ip:port
devkit-url: https://ip:port
# SSL certificate
ssl-key: your_ssl_key

View File

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2019-2029 geekidea(https://github.com/geekidea)
~
~ 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.
-->
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
<!-- localRepository
| The path to the local repository maven will use to store artifacts.
|
| Default: ~/.m2/repository
<localRepository>/path/to/local/repo</localRepository>
-->
<pluginGroups>
</pluginGroups>
<proxies>
</proxies>
<servers>
</servers>
<mirrors>
<mirror>
<id>aliyun-maven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<mirrorOf>central</mirrorOf>
</mirror>
</mirrors>
<profiles>
</profiles>
</settings>

View File

@ -0,0 +1,138 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.opengauss</groupId>
<artifactId>visualtool-parent</artifactId>
<version>${admin.version}</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>pom</packaging>
<artifactId>cov-analysis</artifactId>
<description>
cov-analysis
</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<project.version>7.0.0-RC3</project.version>
<java.version>17</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<maven.compiler.parameters>true</maven.compiler.parameters>
</properties>
<dependencies>
<dependency>
<groupId>org.opengauss</groupId>
<artifactId>visualtool-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.opengauss</groupId>
<artifactId>visualtool-service</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.opengauss</groupId>
<artifactId>visualtool-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.opengauss</groupId>
<artifactId>jsch-session-pool</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.opengauss</groupId>
<artifactId>openGauss-datakit-agent</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.opengauss</groupId>
<artifactId>visualtool-framework</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>${jacoco-maven-plugin.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<version>${maven-clean-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>versions-maven-plugin</artifactId>
<version>${versions-maven-plugin.version}</version>
<configuration>
<generateBackupPoms>false</generateBackupPoms>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<compilerArguments>
<bootclasspath>${java.home}/lib/rt.jar</bootclasspath>
</compilerArguments>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>${jacoco-maven-plugin.version}</version>
<executions>
<execution>
<id>merge-results</id>
<phase>test</phase>
<goals>
<goal>merge</goal>
</goals>
<configuration>
<fileSets>
<fileSet>
<directory>${project.basedir}/../</directory>
<includes>
<include>./**/target/jacoco.exec</include>
</includes>
</fileSet>
</fileSets>
<destFile>${project.basedir}/target/aggregate.exec</destFile>
</configuration>
</execution>
<execution>
<phase>verify</phase>
<goals>
<goal>report-aggregate</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
<finalName>${project.artifactId}</finalName>
</build>
</project>

View File

@ -0,0 +1,84 @@
**openGauss DataKit Agent 文档**
# **关于本手册**
## **Agent介绍**
DataKit 在用户添加管理服务器时可完成服务器Agent安装和启动。DataKit通过给代理Agent服务下发热问你并由代理Agent实现任务执行。
任务类型包括:命令执行,数据采集等多种任务。具体任务定义通过任务模版方式实现。
## **主要功能介绍**
Agent主要包含以下几个功能特性
- 通过服务器管理安装卸载Agent代理
- 任务管理,负责任务定义、存储、下发以及任务执行状态
- 任务执行引擎,支持多种类型任务执行与调度
- 数据采集通过配置采集任务从不同数据源采集指定数据并返回到DataKit分类存储
## **预期读者**
- 用户
- 测试人员
- 开发人员
## **版本及功能列表**
| **版本号** | **新增功能** | **发布时间** |
|-----------| --- |-----------|
| V7.00-RC2 | 首次发布 | 2025-9-30 |
## **Agent 任务配置规则**
### **概述**
DataKit根据任务配置规则创建响应的任务模版。根据任务模版绑定对应Host或者DB实例从而创建具体执行任务实例。在DataKit启动时将任务实例下发给相关代理Agent进程完成任务执行。
### **任务模板定义**
#### 采集任务名称
任务名称不可重复,约定一个采集任务采集一组相关指标数据,并安装一致的存储策略存储在对应表中。
#### 采集任务类型
任务类型包含OSHI固定指标任务OSHI动态指标任务OS指标任务DB指标任务OS管道任务DB管道任务
- OSHI固定指标任务通过OSHI采集服务器固定指标任务
- OSHI动态指标任务通过OSHI采集服务器动态指标任务
- OS指标任务通过配置OS命令采集OS相关指标任务
- DB指标任务通过配置数据库示例通过SQL采集数据库相关数据
- OS管道任务通过配置Linux命令采集相关指定数据
#### 采集任务所属分组
对运行在Agent的采集任务进行分组。待设计
#### 采集任务所属插件
当前任务归属插件ID或者平台基座
#### 采集任务操作对象类型
任务执行对象类型包括OSHIOSDBHTTP
#### 采集任务操作对象
获取任务数据需要执行命令可以是OS命令查询SQL/explain语句,或者部分操作数据库命令(白名单)
- OSsh xx.sh /python yy.py/ top / free -g
- DB :JDBC执行对应SQL仅支持select/explain
- OSHI: Agent内置OSHI框架并提供部分操作系统指标数据采集实现
- HTTP实现数据接收以及转发处理。提供固定数据接收地址以及默认转发地址并运行任务自定义转发地址
#### 采集任务频率
采集频率为任务执行间隔默认单位秒使用ISO-8601持续时间规则表示
- 频率>=0 执行单位为妙eg. P1Y2M3W4DT5H6M7.5S
- 频率==0 任务为一次性任务,执行完任务后立即退出 eg. PT0S
- 频率==-1 任务启动后不主动退出,常驻内存,直到用户主动关闭任务 eg. PT-1S
#### 采集任务上报指标
上报指标定义了当前任务指定采集的数据字段名称,数据描述信息,数据类型,数据单位,采集命令,属性等信息。
模板定义的采集字段详细信息通过t_task_schema_definition表绑定之间的关系。
#### 采集任务上报数据存储策略
采集任务数据存储策略包括,自定义存储策略,实时存储策略,历史存储策略,指纹存储策略,树状存储策略
- 自定义存储策略:开发者自行增加数据处理逻辑,实现数据处理。
- 实时存储策略保持一个周期内的实时采集任务数据不记录历史数据。指标任务数据存储在固定表agent_metric_real_time中管道任务表根据任务动态生成相应实时数据存储表。
- 历史存储策略保存一定历史周期内是采集任务数据。指标任务数据存储在固定表agent_metric_historical中管道任务根据任务名称动态生成对应的历史表
- 指纹去重策略:任务收集数据按照行表存储,并增加指纹去重算法。去重必须指定相关字段名称。(待开发)
- 树状结构存储策略:动态创建专属任务数据表(待开发)
#### 采集任务上报数据保留周期
用于定期清理历史类型任务表存储的过期数据。配置格式采用ISO-8601持续时间规则表示。
#### 采集任务上报数据接收地址
数据接收地址,可选填。开发者指定其他地址,需自行处理数据接收以及数据处理逻辑。
当前支持的数据接收地址包括:
- /receive/fixed/host/info : 默认接收Host固定指标数据并持久化到Host基本信息表
- /receive/metrics 默认接收DataKitOTel数据并将指标数据存储到任务指定表中
- /receive/pipeline 默认接收DataKit可变类型数据并将数据存储到任务指定表中。

View File

@ -0,0 +1,272 @@
## 集成配置
项目基座代码已经配置完成,无需改动。
### 引入repository声明
```XML
<repositories>
<repository>
<id>gitee-eb-repo</id>
<name>The Maven Repository on Gitee</name>
<url>https://gitee.com/agile-adept-team/spring-brick-eb/tree/master/maven-repo</url>
</repository>
</repositories>
```
### 引入框架依赖
```XML
<dependency>
<groupId>com.gitee.starblues</groupId>
<artifactId>spring-brick</artifactId>
<version>3.1.0</version>
</dependency>
```
### 配置文件加入配置
```YAML
plugin:
mainPackage: org.opengauss.admin
runMode: dev
pluginPath:
- ~/visualtool-plugin
```
> `mainPackage`: 主程序最大范围包名。
>
> ```
> runMode`: 运行模式。可配置`dev`/`prod
> ```
>
> `pluginPath`: 插件目录或者插件上级目录,可配置多个。
### Spring-Boot 启动类改造
```Java
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class AdminApplication implements SpringBootstrap {
public static void main(String[] args) {
SpringMainBootstrap.launch(AdminApplication.class, args);
System.out.println("(♥◠‿◠)ノ゙ 服务启动成功 ლ(´ڡ`ლ)゙");
}
@Override
public void run(String[] args) throws Exception {
// 在该实现方法中, 和SpringBoot使用方式一致
SpringApplication.run(AdminApplication.class, args);
}
}
```
### 配置maven打包
```XML
<build>
<plugins>
<plugin>
<groupId>com.gitee.starblues</groupId>
<artifactId>spring-brick-maven-packager</artifactId>
<version>3.1.0</version>
<configuration>
<mode>main</mode>
<mainConfig>
<mainClass>org.opengauss.admin.AdminApplication</mainClass>
<packageType>jar</packageType>
</mainConfig>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.3.0</version>
<executions>
<execution>
<id>copy-web</id>
<phase>generate-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${basedir}/src/main/resources</outputDirectory>
<resources>
<resource>
<directory>../visualtool-ui/dist</directory>
<targetPath>static</targetPath>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<finalName>${project.artifactId}</finalName>
</build>
```
## 插件分类
### **增强插件**
不能独立运行的插件,而是对现有某功能的增强,包括数据和接口实现,因此又可以细分为接口扩展和数据扩展。插件调用通常是在某个业务选项列表中增加可选项,选择后进行调用或者使用。
> **接口扩展**插件本身包含数据和业务逻辑数据通常在插件安装时添加到主程序的数据表中业务逻辑在主程序中类似于service方法一样的调用。
>
> **数据扩展**:插件只包含数据,在插件安装时添加到主程序的数据表中。比如通过插件添加某一类数据资源等。
### **功能插件**
能独立运行的插件,独立实现某个完整的功能或操作,比如某某模块。插件调用通常是在系统内添加菜单入口。
## 增强扩展
### 接口扩展
#### 定义接口
定义将来需要进行扩展的业务接口,比如示例中的通知接口,为了示例的简洁,已省略注释,如下:
```Java
public interface NoticeExtract {
public void send(NoticeDTO noticeDTO);
}
```
#### 实现接口
接口实现分类两类,定义如下:
> 默认实现:在系统中实现插件接口中各个方法。
>
> 扩展实现:在插件中实现插件接口中的各个方法,进行扩展。
不管是哪一类的实现,都需要在类上使用注解进行标记,标记的目的是为了在容器中能定位和调用到到该实现类。
##### 注解
@Extract(bus = "业务标识", scene="场景标识", useCase="用例标识")
> bus业务标识必填比如notice
>
> scene场景标识选填比如smsNotice
>
> useCase用例标识选填比如smsNotice
##### 案例
**默认自带实现,短信通知**
```Java
@Service
public class SmsNoticeImpl implements NoticeExtract {
@Override
public void send(NoticeDTO noticeDTO) {
System.out.println("通过短信发送通知,电话号码是:" + noticeDTO.getTelephone() + ",消息内容是:" + noticeDTO.getContent());
}
}
```
**插件扩展实现,邮件通知**
```Java
@Extract(bus = "notice", scene = "emailNotice")
public class EmailNoticeServiceImpl implements NoticeExtract {
@Override
public void send(NoticeDTO noticeDTO) {
System.out.println("通过邮件发送通知,地址是是:" + noticeDTO.getEmail() + ",消息内容是:" + noticeDTO.getContent());
}
}
```
#### 插件调用
默认实现调用,可以通过@Autowired注入
```Java
public class NoticeController {
//注入默认实现
@Autowired
private NoticeExtract noticeExtract;
@GetMapping("/send")
@ApiOperation(value = "发送通知", notes = "发送通知")
public AjaxResult send() {
NoticeDTO noticeDTO = new NoticeDTO();
noticeDTO.setContent("你好,发送通知");
noticeDTO.setTelephone("17308404741");
noticeDTO.setEmail("liboxiex@gmail.com");
noticeExtract.send(noticeDTO);
return AjaxResult.success();
}
}
```
调用后的结果如下:
> 通过短信发送通知电话号码是17308404741消息内容是你好发送通知
插件实现调用,不能通过@Autowired或者@Resource直接注入NoticeExtract。而是注入ExtractFactory通过ExtractFactory传入实现类的标识通过标识定位到具体的实现并且调用。示例
```Java
public class NoticeController {
@Autowired
private ExtractFactory extractFactory;
//默认实现
// @Autowired
// private NoticeExtract noticeExtract;
@GetMapping("/send")
@ApiOperation(value = "发送通知", notes = "发送通知")
public AjaxResult send() {
String bus = "notice";
String scene = "emailNotice";
// 调用插件实现
NoticeExtract noticeExtract = extractFactory.getExtractByCoordinate(ExtractCoordinate.build(bus, scene, ""));
NoticeDTO noticeDTO = new NoticeDTO();
noticeDTO.setContent("你好,发送通知");
noticeDTO.setTelephone("17308404741");
noticeDTO.setEmail("liboxiex@gmail.com");
noticeExtract.send(noticeDTO);
return AjaxResult.success();
}
}
```
调用结果如下:
> 通过邮件发送通知地址是liboxiex@gmail.com消息内容是你好发送通知。
### 数据扩展
提供对应的数据添加和删除方法比如服务器资源信息未来需要扩展则创建服务器资源service并且增加save和delete方法。在插件的安装、启动、停止、卸载时调用save和delete方法进行数据操作。
> **save方法可以避免数据重复添加delete方法可以根据非主键字段删除。**
## 功能扩展
功能扩展原则上没有对主程序的强依赖,只需要遵循插件开发手册,开发完整功能即可。

View File

@ -0,0 +1,144 @@
# OpenGauss Datakit 开发环境搭建流程
## 推荐环境
- java 17+
- maven 3.9.0+
- node v18+含npm
此外为保证依赖下载顺利需配置好maven镜像源和node镜像源。
## 后端
### 工具
- IDEIntellij IDEA 2023.3.3
- OSOpenEuler 22.03
- JDKJava version: 17.0.10, vendor: Oracle Corporation
- Maven3.9.0+
### 准备
DataKit版本7.0.0-RC2开始项目基础平台升级为Springboot 3.5.6,在项目编译前需本地编译且安装SpringBrick组件。
1、下载SpringBrick项目源码命令如下
```
git clone https://gitcode.com/wang4721/springboot-plugin-framework-parent.git
```
2、进入springboot-plugin-framework-parent项目根目录使用Maven进行编译安装
```
mvn clean install -Dmaven.test.skip=true
```
3、执行成功后从gitcode上下载DataKit项目命令如下
```
git clone https://gitcode.com/opengauss/openGauss-workbench.git
```
安装好OpenGauss详细步骤见[README.md](https://gitee.com/opengauss/openGauss-workbench/tree/master/README.md)。将整个openGauss-workbench文件夹导入IDEA。想调试插件的话应该先运行一次根目录的`build.sh`,命令如下:
```shell
sh build.sh
```
### visualtool-api Debug流程
项目以`visualtool-*`为核心框架Debug流程时启动这个核心框架后再动态加载`plugins`下的各个插件。
要启动核心框架需更改dev环境的配置文件它在`openGauss-datakit/visualtool-api/src/main/resources/`下,也就是`application-dev.yml`文件修改其中的url, username, password等字段示例如下
```ymal
system:
defaultStoragePath: /ops/debug_files
whitelist:
enabled: false
server:
port: 9494
servlet:
context-path: /
logging:
file:
path: ./logs/
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
# For openGauss
driver-class-name: org.opengauss.Driver
url: jdbc:opengauss://127.0.0.1:5432/db_datakit?currentSchema=public&batchMode=off
username: opengauss_test
password: Sample@123
swagger:
enabled: true
pathMapping: /dev-api
plugin:
sortInitPluginIds:
decrypt:
enable: false
className: com.gitee.starblues.common.cipher.AesPluginCipher
plugins:
- pluginId: example-basic-1
props:
secretKey: mmfvXes1XckCi8F/y9i0uQ==
version: 0.0.0
```
配好`application-dev.yml`就可以调试了,这时用`visualtool-api`中的`AdminApplication.java`中的`org.opengauss.admin.AdminApplication`类启动调试即可。
### plugins Debug流程
以上只启动了`visualtool-*`这个核心框架,想要调试`plugins`文件夹下的各个插件,则需要在项目根目录下创建`visualtool-plugin`文件夹,将`sh build.sh`生成的各插件的jar拖到这个文件夹下重新启动框架即可。
### 前后端联调
如果您有前后端联调的需求,同时也为方便起见,应在`openGauss-datakit/visualtool-framework/src/main/java/org/opengauss/admin/framework/config/SecurityConfig.java`这个文件中,
- 修改`.anyRequest().authenticated()`字段为`.anyRequest().permitAll()`
- 修改`.headers().frameOptions().disable()`为`.headers().disable()`
示例如下:
```java
...
httpSecurity
...
.antMatchers("/prometheus").permitAll()
// .anyRequest().authenticated()
.anyRequest().permitAll()
.and()
// .headers().frameOptions().disable();
.headers().disable();
...
```
这样修改后并非意味着一定不需要密码登录。这样做会有以下效果
- 前端各个插件`yarn dev`时可以连接到后端,否则无法连接(需要登录)
- 前端访问一些涉及到查询登录用户的个人信息的api时仍然会无法访问因为没有登录但是绝大部分前端功能还是能用的
- 访问后端`visualtool-ui`的`:9494`这个端口时还是会进入登录界面
- 在修改代码重新运行后端的情况下不刷新浏览器界面即F5或者浏览器地址栏Enter一下则不需要重新登录刷新界面则需要在浏览器控制台运行如下命令清空一下localstorage
```javascript
localStorage.clear();
```
否则会卡在某个地方,建议遇到任何未知错误时都`localStorage.clear();`一下
至此,后端环境搭建部分告一段落。
## 前端
### 工具
- Nodev18.19.0
- npm10.2.3
- yarn1.22.21
### 前端环境搭建
前端调试需要先启动相应功能的后端服务。这里以调试base_ops插件的前端为例后端自然需要将`base-ops-6.0.0-repackage.jar`放入`visualtool-plugin`中,同时运行以下命令:
```shell
cd plugins\base-ops\web-ui
# 执行npm install安装依赖
npm install
# 然后执行 npm run dev 或者 yarn dev 启动前端服务
yarn dev
```
启动完成后执行窗口会输出Local地址在本例中为http://localhost:8081/static-plugin/base-ops/,然后就可以登录调试了。
### 可能遇到的问题
```
Watchpack Error (watcher): Error: ENOSPC: System limit for number of file watchers reached, watch 'xxxx'
```
参考StackOverflow上这个帖子[React Native Error: ENOSPC: System limit for number of file watchers reached](https://stackoverflow.com/questions/55763428/react-native-error-enospc-system-limit-for-number-of-file-watchers-reached)。
解决办法是更改系统设置如下:
```shell
# insert the new value into the system config
echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
# check that the new value was applied
cat /proc/sys/fs/inotify/max_user_watches
# config variable name (not runnable)
fs.inotify.max_user_watches=524288
```

View File

@ -0,0 +1,141 @@
## 技术选型
- vue3
- 构建工具vue-cli
- UI框架arco.design
- 路由vue-router
- 状态管理器pinia
- 图表echarts
## 开发与部署
推荐使用yarn包管理工具
### **安装依赖**
```Bash
yarn
```
### **开发环境**
```Bash
# 配置.env.development中的VUE_APP_BASE_API默认为访问接口地址的/dev-api
# 访问本地后端服务需修改vue.config.js的proxy的target地址
yarn dev
```
### **生产构建**
```Bash
yarn build
```
### **代码校验**
```Bash
yarn lint
```
## 开发规范
### **开发工具**
- 统一使用vs code开发
- 前端依赖包安装使用yarn镜像源使用淘宝镜像
- 编码时tab统一为2个空格
### **命名规范**
- 组件目录使用小驼峰命名方式多单词拼接首字母写userInfo
- 组件文件名使用大驼峰命名方式多单词拼接首字母写Manage.vue
- 变量名、方法名遵循驼峰命名规则多单词拼接首字母写userInfo
- 变量声明letconst定义常量使用const定义变量尽量使用let
### **使用ts es6风格进行编码**
- 解构赋值
- 箭头函数
- 正确使用模块,如果模块只有一个输出值,就使用 export default如果模块有多个输出值就不使用 export defaultexport default 与普通的 export 尽量不要同时使用
- 多个异步操作时使用Promise对象进行封装
- 除了三目运算if,else 等禁止简写
- 有ts红色警告的地方需要进行处理
### **注释规则**
- 公共组件的使用说明
- 各组件中响应变量、重要函数或者类的说明
- 复杂的业务逻辑处理说明
- 已注释掉的代码要说明注释原因
- 多重 if 判断语句需添加说明
- 注释块必须以/**多行说明 */,单行注释使用//
### **指令规范**
- v-for 循环必须加上 key 属性,在整个 for 循环中 key 需要唯一
- 避免 v-if 和 v-for 同时用在一个元素上
- props 定义应该尽量详细
### **目录说明**
- api 前端接口目录其中interceptor.ts为统一拦截器
- assets 静态文件如图片及公共样式icons为svg图标images是png图片目录style为公共样式
- components 公共组件目录
- config 全局配置,主题、菜单获取方式、菜单宽度、菜单是否默认收起等
- hooks 公共钩子方法
- layout 布局组件
- router 前端路由,菜单若采用了后端接口获取,此目录作为前端开发时使用的路由配置
- store 状态管理目录使用的是pinia
- types ts类型配置目录
- utils 公共工具目录
- views 页面目录按文件夹划分单个页面中使用的业务组件需新建components文件夹放在其中
### **样式规范**
- 界面级别vue组件使用统一dom结构
```HTML
<template>
<div class="app-container">
<div class="main-bd">
<!-- 其他html -->
</div>
</div>
</template>
<!-- 样式统一按如下形式包裹起来,只有一级 -->
<style lang="less" scoped>
.app-container {
.main-bd {
/* 其他样式 */
}
}
</style>
```
- 组件的dom结构
```HTML
<template>
<div class="${组件名}-container">
<!-- ${组件名}是要替换的如user-dialog -->
<!-- 其他html -->
</div>
</template>
<!-- 样式统一按如下形式包裹起来,只有一级 -->
<style lang="less" scoped>
.user-dialog-container {
/* 其他样式 */
}
</style>
```
- 颜色值统一用css变量不另外写色值便于颜色的统一管理
- 自定义的css变量统一在src/assets/style/global.less文件里面管理
### **提交规范**
- 提交前先更新代码
- 提交前执行yarn lint进行代码格式校验
- 提交说明须简要描述当次提交的内容

View File

@ -0,0 +1,302 @@
# 菜单接口
用于在插件安装和卸载是添加、删除插件自身菜单。
## **接口路径**
org.opengauss.admin.system.plugin.facade.MenuFacade
## **方法**
### savePluginMenu
添加菜单以及前端路由
**入参**
| 参数名 | 类型 | 是否必填 | 说明 |
| ---------- | ------- | -------- | ------------------------------------------------- |
| pluginId | String | 是 | 插件标识需要和pom中<pluginInfo>下的id保持一致。 |
| menuName | String | 是 | 菜单名称 |
| menuEnName | String | 是 | 菜单英文名称 |
| order | Integer | 是 | 菜单排序 |
| path | String | 否 | 菜单路由路径默认为index。 |
| parentId | Integer | 否 | 父菜单,默认为根目录。 |
| openWay | Integer | 否 | 打开方式暂时仅支持页面打开默认为1。 |
**出参**
返回MenuVo对象详情见下文。
**调用案例**
```Java
//插件监听事件中获取Menufacade仅显示相关代码
MainApplicationContext context = ((ApplicationReadyEvent) event).getApplicationContext().getBean(MainApplicationContext.class);
SpringBeanFactory factory = context.getSpringBeanFactory();
MenuFacade menuFacade = factory.getBean(MenuFacade.class);
menuFacade.savePluginMenu("plugin-book","图书管理插件","books manager","index",0);
```
### deletePluginMenu
根据pluginId删除该插件下所有菜单与路由数据。
**入参**
| 参数名 | 类型 | 是否必填 | 说明 |
| -------- | ------ | -------- | ------------------------------------------------- |
| pluginId | String | 是 | 插件标识需要和pom中<pluginInfo>下的id保持一致。 |
**出参**
返回MenuVo对象详情见下文。
**调用案例**
```Java
//插件监听事件中获取Menufacade仅显示相关代码
MainApplicationContext context = ((ContextClosedEvent) event).getApplicationContext().getBean(MainApplicationContext.class);
SpringBeanFactory factory = context.getSpringBeanFactory();
MenuFacade menuFacade = factory.getBean(MenuFacade.class);
menuFacade.deletePluginMenu("plugin-book");
```
### savePluginRoute
添加插件非菜单路由。
**入参**
| 参数名 | 类型 | 是否必填 | 说明 |
| ---------- | ------- | -------- | ------------------------------------------------------------ |
| pluginId | String | 是 | 插件标识需要和pom中<pluginInfo>下的id保持一致。 |
| menuName | String | 是 | 路由名称 |
| menuEnName | String | 是 | 路由英文名称 |
| path | String | 否 | 路由路径默认为index如需传递参数则使用query的方式传参并使用例如index?id=${id}。 |
| parentId | Integer | 否 | 父菜单,默认为根目录。 |
**出参**
返回MenuVo对象详情见下文。
### saveIndexInstanceRoute
添加首页实例路由。
**入参**
| 参数名 | 类型 | 是否必填 | 说明 |
| -------- | ------ | -------- | ------------------------------------------------------------ |
| pluginId | String | 是 | 插件标识需要和pom中<pluginInfo>下的id保持一致。 |
| menuName | String | 是 | 菜单名称 |
| path | String | 否 | 菜单路由路径默认为index如需传递参数则使用query的方式传参并使用例如index?id=${id}。 |
**出参**
返回MenuVo对象详情见下文。
**调用案例**
**调用案例**
```Java
//插件监听事件中获取Menufacade仅显示相关代码
MainApplicationContext context = ((ContextClosedEvent) event).getApplicationContext().getBean(MainApplicationContext.class);
SpringBeanFactory factory = context.getSpringBeanFactory();
MenuFacade menuFacade = factory.getBean(MenuFacade.class);
menuFacade.saveIndexInstanceRoute("moniter-plugin","实例监控插件","index");
```
# 插件接口
用于获取插件本身相关的数据。
## 接口路径
org.opengauss.admin.system.plugin.facade.PluginFacade
## 方法
### getPluginConfigData
获取插件配置数据
**入参**
| 参数名 | 类型 | 是否必填 | 说明 |
| -------- | ------ | -------- | ------------------------------------------------- |
| pluginId | String | 是 | 插件标识需要和pom中<pluginInfo>下的id保持一致。 |
**出参**
返回json对象字符串其中key为插件jar包中配置的**attrCode**。示例:
```JSON
{
"esHost": "192.168.1.104",
"esPort": "23552"
}
```
# openGauss实例接口
用于获取openGauss集群及实例信息
## 接口路径
org.opengauss.admin.system.plugin.facade.OpsFacade
## 方法
### listCluster
查询集群列表
**入参**
**出参**
List<OpsClusterVO> 对象,详见对象结构说明。
# WebScoket接口
用于通过主程序向客户端推送消息。
## 接口路径
org.opengauss.admin.system.plugin.facade.WsFacade
## 方法
### sendMessage
推送消息到WebSocket客户端。
**入参**
| 参数名 | 类型 | 是否必填 | 说明 |
| --------- | ------ | -------- | ----------------------------------------------- |
| pluginId | String | 是 | 插件标识需要和pom中<pluginInfo>下的id保持一致 |
| sessionId | String | 是 | 会话ID |
| message | String | 是 | 消息内容 |
# JDBC实例接口
用于获取JDBC集群及实例信息
## 接口路径
org.opengauss.admin.system.plugin.facade.JdbcDbClusterFacade
## 方法
### page
查询集群列表
**入参**
name : 模糊搜索关键字
page 分页参数
**出参**
Page<JdbcDbClusterVO> 对象,详见对象结构说明。
# 物理机资源列表
用于获取物理机资源信息
## 接口路径
org.opengauss.admin.system.plugin.facade.HostFacade
## 方法
### listAll
获取物理机资源列表
**入参**
**出参**
List<OpsHostEntity> 对象,详见对象结构说明。
# 对象结构说明
**MenuVo**
| 属性名 | 说明 |
| -------- | -------------- |
| menuId | 菜单或路由ID |
| menuName | 菜单及路由名称 |
| parentId | 父级ID |
**OpsClusterVO**
| 属性名 | 说明 |
| ---------------- | ------------------------------------------------------- |
| clusterId | 集群标识 |
| clusterName | 集群名称 |
| version | 集群版本 ENTERPRISE企业版/MINIMAL_LIST极简版/LITE轻量版 |
| versionNum | 集群版本号 |
| databasePassword | 集群密码 |
| clusterNodes | 集群节点信息 |
**OpsClusterNodeVO**
| 属性名 | 说明 |
| -------------- | ------------ |
| nodeId | 节点标识 |
| clusterRole | 节点角色 |
| publicIp | 公网IP |
| privateIp | 内网IP |
| hostname | 主机名 |
| azName | AZ名 |
| dbPort | 数据库端口 |
| dbName | 数据库名 |
| dbUser | 数据库用户名 |
| dbUserPassword | 数据库密码 |
| hostPort | ssh端口 |
| rootPassword | root用户密码 |
**JdbcDbClusterVO**
| 属性名 | 说明 |
| ---------- | ------------------------------ |
| clusterId | 集群ID |
| name | 集群名称 |
| deployType | 部署类型参考DeployTypeEnum |
| dbType | 数据库类型: 参考DbTypeEnum |
| nodes | 节点,参考 JdbcDbClusterNodeVO |
**JdbcDbClusterNodeVO**
| 属性名 | 说明 |
| ------------- | ---------------- |
| clusterNodeId | 节点ID |
| name | 节点名称 |
| ip | 节点ip地址 |
| port | 节点端口 |
| username | 数据库连接用户名 |
| password | 数据库连接密码 |
| url | 数据库连接串 |
**OpsHostEntity**
| 属性名 | 说明 |
| --------- | ------------ |
| hostId | 主机ID |
| hostname | 主机hostname |
| privateIp | 内网ip地址 |
| publicIp | 公网ip地址 |
| port | ssh端口 |
| azId | 所属AZ的ID |

View File

@ -0,0 +1,838 @@
# 插件分类
**增强插件**:不能独立运行的插件,而是对现有某功能的增强,包括数据和接口实现,因此又可以细分为接口扩展和数据扩展。插件调用通常是在某个业务选项列表中增加可选项,选择后进行调用或者使用。
> **接口扩展**插件本身包含数据和业务逻辑数据通常在插件安装时添加到主程序的数据表中业务逻辑在主程序中类似于service方法一样的调用。
>
> **数据扩展**:插件只包含数据,在插件安装时添加到主程序的数据表中。比如通过插件添加某一类数据资源等。
**功能插件**:能独立运行的插件,独立实现某个完整的功能或操作,比如某某模块。插件调用通常是在系统内添加菜单入口。
# 插件开发
## 集成配置
### 引入依赖
#### 引入SpringBoot依赖
- 如果插件的`spring-boot`版本与主程序**一致**,则引入如下依赖
```XML
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.5.6</version>
</dependency>
```
- 如果插件的`spring-boot`版本与主程序**不一致**,则引入如下依赖
```XML
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>${spring-boot.version}</version>
</dependency>
```
**推荐跟主程序的springboot保持一致版本**
#### 引入插件依赖
```XML
<dependency>
<groupId>com.gitee.starblues</groupId>
<artifactId>spring-brick-bootstrap</artifactId>
<version>3.1.0</version>
</dependency>
```
#### 引入主程序依赖
```XML
<dependency>
<groupId>org.opengauss</groupId>
<artifactId>visualtool-service</artifactId>
<version>6.0.0</version>
<!--如果不需要操作数据库和redis则排除以下依赖-->
<exclusions>
<exclusion>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-3-starter</artifactId>
</exclusion>
<exclusion>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</exclusion>
<exclusion>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
```
#### 定义插件引导类
> 注意插件引导类包必须在主程序的包名之下主程序的包名是org.opengauss.admin因此插件的引导类必须在org.opengauss.admin.xxx之下。
定义插件`main`入口类, 继承`SpringPluginBootstrap`类, 然后在`main`函数中实例化当前引导类,并执行`run`方法即可。实现如下:
```Java
//如果不需要操作数据库和redis则关闭掉相关的自动装配
@SpringBootApplication(exclude={
DataSourceAutoConfiguration.class,
HibernateJpaAutoConfiguration.class,
RedisAutoConfiguration.class,
RedisRepositoriesAutoConfiguration.class
})
public class EmailNoticeApplication extends SpringPluginBootstrap {
public static void main(String[] args) {
new EmailNoticeApplication().run(args);
System.out.printf("邮件通知插件启动成功");
}
}
```
## 接口扩展插件开发
实现主程序中定义的接口,并在各个方法中完成相应的业务逻辑,同时在类上面加上注解@Extract配置该插件独有的标识该标识在主程序中调用时需要用到。
@Extract(bus = "业务标识", scene="场景标识", useCase="用例标识")
> bus业务标识必填比如notice
>
> scene场景标识选填比如smsNotice
>
> useCase用例标识选填比如smsNotice
案例:
```Java
@Extract(bus = "notice", scene = "emailNotice")
public class EmailNoticeServiceImpl implements NoticeExtract {
@Override
public void send(NoticeDTO noticeDTO) {
System.out.println("通过邮件发送通知,地址是是:" + noticeDTO.getEmail() + ",消息内容是:" + noticeDTO.getContent());
}
}
```
## 数据扩展插件开发
1. 定义监听类,在启动和停止事件中,调用主程序菜单接口的添加和删除方法。菜单接口说明详见文档[主程序开放接口](https://fullstack-dao.feishu.cn/docx/doxcnIa9e0ChR4bJWlx4IyBfzjf)
> 在Listener中无法使用@Autowired或者@Resource注入业务操作service因此只能通过注入主程序Bean的第三种方式获取。
案例:
```Java
public class ImplListener implements ApplicationListener<ApplicationEvent> {
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
System.out.println("扩展实现初始化环境变量");
} else if (event instanceof ApplicationPreparedEvent) {
System.out.println("扩展实现环境初始化完成");
} else if (event instanceof ContextRefreshedEvent) {
System.out.println("扩展实现ApplicationContext被刷新");
} else if (event instanceof ApplicationReadyEvent) {
MainApplicationContext context = ((ApplicationReadyEvent) event).getApplicationContext().getBean(MainApplicationContext.class);
SpringBeanFactory factory = context.getSpringBeanFactory();
ISysPluginResourceService service = factory.getBean(ISysPluginResourceService.class);
SysPluginResource resource = new SysPluginResource();
resource.setResourceName("plugin-impl数据资源");
resource.setIp("127.0.0.1");
resource.setUserName("xielibo");
resource.setUserPassword("123123");
service.savePluginResource(resource);
System.out.println("扩展实现插件已经启动完成");
} else if (event instanceof ContextClosedEvent) {
MainApplicationContext context = ((ApplicationReadyEvent) event).getApplicationContext().getBean(MainApplicationContext.class);
SpringBeanFactory factory = context.getSpringBeanFactory();
ISysPluginResourceService service = factory.getBean(ISysPluginResourceService.class);
SysPluginResource resource = new SysPluginResource();
resource.setId(1);
service.deletePluginResource(resource);
System.out.println("扩展实现插件停止");
}
}
}
```
1. 配置监听类
```YAML
context:
listener:
classes: com.fsd.admin.plugins.listener.ImplListener
```
**注意主程序中的service必须有显性的save和delete方法并且save方法可以避免数据重复添加delete方法可以根据非主键字段删除。**
安装/启用插件后将会调用save方法添加数据到对应的表中停止/卸载插件将会调用delete方法将数据删除。
## 功能插件开发
功能插件是运行在主程序上的独立功能的插件是具备完整业务操作的模块。此类插件包括前端代码并且打包在jar包运行。
### 前端技术选型
建议使用vue构建工具使用vue-cli。因vite暂不稳定使用vite带来的部署问题平台不提供支持。
**前端插件中避免使用以下第三方库/插件/工具:**
```Bash
# 构建工具
vite
# 第三方插件
monaco-editor
```
### 前端主题切换
**平台默认为亮色模式**
设置深色模式时,通过**localStorage.setItem('opengauss-theme', 'dark')**存储深色主题的参数到浏览器的localStorage中插件可以通过**localStorage.getItem('opengauss-theme')**获取如果值为dark字符串则是深色模式否则为亮色模式。
**监听平台主题变化**
平台切换主题时会触发bus事件插件页面通过bus事件监听获取相应的主题值变化来进行插件的主题调整监听方法如下
```JavaScript
// 监听 opengauss-theme-change 主题切换事件
window.$wujie?.bus.$on('opengauss-theme-change', val => {
console.log(val)
})
```
### 前端国际化
**平台使用vue-i18n进行国际化**
插件需要进行国际化时,可以直接通过**localStorage.getItem('locale')**获取平台当前的语言进行插件的国际化处理建议也使用vue-i18n插件进行国际化与平台使用的插件保持一致
**监听平台语言变化**
平台切换语言时会触发bus事件插件页面通过bus事件监听获取相应的语言值变化来进行插件的语言调整监听方法如下
```JavaScript
// 监听 opengauss-locale-change 语言切换事件
window.$wujie?.bus.$on('opengauss-locale-change', val => {
console.log(val)
})
```
### 插件间页面跳转示例
在插件里面获取主应用的jump方法进行跳转
```JavaScript
// jump方法的参数是一个route对象
window.$wujie?.props.methods.jump({
name: `${路由name}`,
query: {
xxx: 'abc'
}
})
```
**${路由name}参数**为:固定前缀(Static-plugin+插件ID)+插件路由路径分段的首字母大写。
**例如**
跳转插件路由为/ebbc/abc/get跳转插件ID为check则此处参数为Static-pluginCheckEbbcAbcGet
给插件传参通过url的query参数进行传递在插件中按下面的方法进行获取
```JavaScript
window.$wujie?.props.data.xxx
```
**注:**当跳转插件中的非菜单页面时比如插件的某个详情页需要将此详情页的路由通过主程序的savePluginRoute接口添加为**隐藏菜单路由**,否则无法跳转。
### 前端资源配置
在插件中的配置文件中配置静态资源所在目录
```YAML
spring:
resources:
static-locations: classpath:static
```
### 前端访问路径
**静态资源的访问规则为:****`http://ip:port/static-plugin/插件id/具体插件的资源路径`**
> 插件的前端访问需要结合主程序的菜单、路由框架整体上进行设计。
### 前端vue.config.js配置
为了解决插件页面的css和js等资源文件能正常访问需要在vue.config.js中配置publicUrl为前端固定路径前缀(**`static-plugin`**)+pluginId。案例
```JavaScript
publicPath: "/static-plugin/xielibo-plugin-page/",
```
### 后端API**访问规则**
**在****前端****中请求插件的****api****时需要加上api前缀。例如http://ip:port/{pluginRestPathPrefix}/{pluginId}/,规则如下:**
- `pluginRestPathPrefix`: 主程序配置文件中`pluginRestPathPrefix`的配置值, 默认为: `plugins`
- `pluginId`: 插件id, 如果主程序配置文件中`enablePluginIdRestPathPrefix`配置值为 `true`, 则使用插件id作为前缀否则不使用插件id作为前缀。
**案例**
插件IDxxxx
后端API原始地址/test/get/123
前端调用地址:/plugins/xxxx/test/get/123
### 添加到主程序菜单
1. 定义监听类,在启动和停止事件中,调用主程序菜单接口的添加和删除方法。菜单接口说明详见文档
[主程序开放接口](https://fullstack-dao.feishu.cn/docx/doxcnIa9e0ChR4bJWlx4IyBfzjf)
```Java
public class MyListener implements ApplicationListener<ApplicationEvent> {
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
System.out.println("扩展实现初始化环境变量");
} else if (event instanceof ApplicationPreparedEvent) {
System.out.println("扩展实现环境初始化完成");
} else if (event instanceof ContextRefreshedEvent) {
System.out.println("扩展实现ApplicationContext被刷新");
} else if (event instanceof ApplicationReadyEvent) {
//此处只能通过注入主程序Bean的第三种方式获取
MainApplicationContext context = ((ApplicationReadyEvent) event).getApplicationContext().getBean(MainApplicationContext.class);
SpringBeanFactory factory = context.getSpringBeanFactory();
MenuFacade menuFacade = factory.getBean(MenuFacade.class);
menuFacade.savePluginMenu("plugin-book", "图书管理插件", "books manager", "index");
System.out.println("扩展实现插件已经启动完成");
} else if (event instanceof ContextClosedEvent) {
MainApplicationContext context = ((ContextClosedEvent) event).getApplicationContext().getBean(MainApplicationContext.class);
SpringBeanFactory factory = context.getSpringBeanFactory();
MenuFacade menuFacade = factory.getBean(MenuFacade.class);
menuFacade.deletePluginMenu("plugin-book");
System.out.println("扩展实现插件停止");
}
}
}
```
1. 配置监听类
```YAML
context:
listener:
classes: com.fsd.admin.plugins.listener.MyListener
```
## 操作日志打印
平台提供了操作日志异步打印的工具。在插件脚手架的**LogAspect类**中已经集成在需要打印的controller的方法上使用自定义注解@Log即可。该注解有以下参数
| **参数名** | **类型** | **说明** |
|--------------------|--------------|-------------------------------------------------|
| title | string | 所属模块建议格式“插件ID”-模块。 |
| businessType | BusinessType | 操作类型,包括(*OTHER、INSERT、UPDATE、DELETE等*默认为OTHER。 |
| operatorType | OperatorType | 操作来源传入枚举类OperatorType.*PLUGIN*。 |
| isSaveRequestData | boolean | 是否保存请求参数。默认为true |
| isSaveResponseData | boolean | 是否保存响应参数。默认为true |
示例:
```Java
/**
* 添加
*/
@Log(title = "base-ops-dataflow",operatorType = OperatorType.PLUGIN,businessType = BusinessType.INSERT)
@RequestMapping("/add")
public AjaxResult add(@RequestBody ModelingDataFlowEntity dataFlowData) {
return toAjax(modelingDataFlowService.insertDataFlow(dataFlowData));
}
```
## 登录用户Token传参
平台登录后会将用户token存储在浏览器的localStorage中名称为**opengauss-token**。
在插件中获取token并传参有如下2步
1. 通过**localStorage.getItem('opengauss-token')**获取平台存储的登录用户token
2. 在每个请求发起的地方在请求头header中加上**Authorization**参数,值为**'Bearer 上一步中获取的token'**。建议使用axios直接在axios拦截器的请求头拦截中给每个请求加上headers。
示例:
```JavaScript
axios.interceptors.request.use(
(config) => {
const token = localStorage.getItem('opengauss-token')
if (token) {
if (!config.headers) {
config.headers = {}
}
config.headers.Authorization = `Bearer ${token}`
}
return config
},
(error) => {
// do something
return Promise.reject(error)
}
)
```
## 插件授权扩展信息
平台支持插件自定义上报插件扩展信息及插件升级授权,主程序中定义了插件扩展信息的接口,插件只需实现扩展信息接口,即可上报插件信息到主程序中。
### PluginExtensionInfoExtract接口
该接口用于扩展插件信息,插件需要实现该扩展接口,实现`getPluginExtensionInfo`方法并且在实现类上面添加Extract注解。
#### Extract注解
用于标识该实现扩展的唯一性,方便在主程序中调用时能定位到具体的实现。 其中有三个值:
* `bus`: 业务标识,必须使用 **插件id**。【必选】
* `scene`: 场景标识。【可选】
* `useCase`: 用例标识。 【可选】
#### 示例
```java
@Extract(bus = "visualtool-plugin")
public class ExtensionInfoServiceImpl implements PluginExtensionInfoExtract {
@Override
public PluginExtensionInfoDto getPluginExtensionInfo() {
PluginExtensionInfoDto dto = new PluginExtensionInfoDto();
dto.setPluginId("visualtool-plugin");
dto.setPluginName("visualtool plugin");
dto.setPluginHome("https://opengauss.org/zh/");
dto.setPluginDevelopmentCompany("openGauss");
dto.setPhoneNumber("400-400-4000");
dto.setEmail("contact@opengauss.org");
dto.setCompanyAddress("xx路xx号");
dto.setAuthAddress("/test-plugin/license");
dto.setPluginLicenseType(PluginLicenseType.FREE);
dto.setPluginExpirationTime(new Date());
return dto;
}
}
```
#### 参数说明
在 PluginExtensionInfoDto 中有以下参数,插件可以在接口实现中自定义对应的参数值,来完善插件扩展信息:
| 参数 | 类型 | 是否必填 | 可选值 | 说明 |
|--------------------------|-------------------|------|------------------------------|----------------------------------|
| pluginId | String | 是 | - | 插件id |
| pluginLicenseType | PluginLicenseType | 是 | OPEN_SOURCEFREE TRIALPAID | 插件类型(开源|免费|试用|正式) |
| pluginName | String | 否 | - | 插件名称 |
| pluginIntroduction | String | 否 | - | 插件简介 |
| pluginHome | String | 否 | - | 插件主页 |
| pluginActivationTime | Date | 否 | - | 插件激活时间 |
| PluginExpirationTime | Date | 否 | - | 插件有效期 |
| pluginDevelopmentCompany | String | 否 | - | 开发公司 |
| phoneNumber | String | 否 | - | 联系电话 |
| email | String | 否 | - | 联系邮箱 |
| companyAddress | String | 否 | - | 公司地址 |
| userGuide | String | 否 | - | 使用指导 |
| demoAddress | String | 否 | - | demo地址 |
| authAddress | String | 否 | - | 授权地址(可以是插件内部的一个路由跳转地址或外部的一个跳转链接) |
| customize | String | 否 | - | 自定义信息(暂不使用) |
## Websocket使用
插件框架本身不支持在插件内使用websocket为了支持插件内可以使用ws的能力主程序中做了对ws请求的转发同时提供推送消息给客户端的接口给插件调用以满足插件的双向通信需求。
### **SocketExtract接口**
该接口用于Websocket的连接与通信插件需要扩展改接口实现**onOpen**、**processMessage、onClose**方法,并**在类上加上Extract注解。**
#### Extract注解
用于标识该实现扩展的唯一性,方便在主程序中调用时能定位到具体的实现。 其中有三个值:
- `bus`: 业务标识建议把插件ID当做前缀。【必选】
- `scene`: 场景标识。【可选】
- `useCase`: 用例标识。 【可选】
示例:
```Java
@Extract(bus = "plugin1-handler")
public class SocketMessageHandler implements SocketExtract {
@Override
public void onOpen(String pluginId, String sessionId, Session session) {
System.out.println("连接成功。。。。。。。。");
}
@Override
public void processMessage(String sessionId, String message) {
System.out.println("接收到消息并处理。。。。。。。。" + message);
}
@Override
public void onClose(String pluginId, String sessionId) {
System.out.println("连接关闭。。。。。。。。");
}
}
```
#### 连接成功
**onOpen方法**会在连接成功时触发插件的回调。
#### 接收消息
**processMessage方法**。该方法会接收到客户端发送的消息。
#### 连接关闭
**onClose方法**会在连接关闭时触发插件的回调。
### 推送消息
调用主程序暴露的**WsFacade接口的sendMessage方法主程序接口注入方式见下文**。该方法有三个参数,分别是:
- `pluginId`: 插件自己的ID。
- `sessionId`: ws会话ID。
- `message`: 消息内容。
示例:
```Java
@Autowired
@AutowiredType(AutowiredType.Type.PLUGIN_MAIN)
private WsFacade wsFacade;
@PostMapping("/send")
public AjaxResult send(String sessionId,String message) {
wsFacade.sendMessage("datasync-mysql", sessionId, message);
return AjaxResult.success();
}
```
### 连接地址
**前端****ws连接地址为**`wss://ip:9494/ws/插件ID/ws会话ID`**
> ws会话ID由插件自己生成和维护。
## 插件中抛出异常
平台已经做了全局异常捕获处理和自定义**异常类CustomException**插件中无需做全局捕获处理只需要抛出该异常类即可传入code和message。
**插件中使用的异常code需要由OPS SIG组分配。**
示例:
```Java
@GetMapping("/test")
@Log(title = "sync-mysql", businessType = BusinessType.OTHER, operatorType = OperatorType.PLUGIN)
public AjaxResult test() {
syncDataService.testLog();
if (1 == 1) {
throw new CustomException("插件中测试抛出全局异常", 伪代码code);
}
return AjaxResult.success();
}
```
调用效果:
![img](https://fullstack-dao.feishu.cn/space/api/box/stream/download/asynccode/?code=YTQyMDhkZDM3NzkwZjFlZjQ3YTdkOGJhNjgwZTM5NmVfd3pXZjZESWZWczQ3Z2JzMVRDbWdyTVNPbDlpd2tXMUNfVG9rZW46Ym94Y25USno1cGRFVk52ZTNDTkZxU0sxcTZnXzE2NzM4NzY5NzY6MTY3Mzg4MDU3Nl9WNA)
## 插件扩展参数配置
在插件中可以配置自定义的参数以提供给平台获取使用比如插件的Logo、插件的主题等。
### 使用方式
在插件中实现com.gitee.starblues.core.PluginExtensionInfo接口接口。例如
```Java
@Component
public class PluginExtensionInfoConfig implements PluginExtensionInfo {
@Override
public Map<String, Object> extensionInfo() {
Map<String, Object> map = new HashMap<>();
map.put("logo", "PHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJDYXBhXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAzMTAgMzEwIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCAzMTAgMzEwOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxwYXRoIGQ9Ik0zMDAuNTY0LDE3OS4zMTFMMjgyLjQsMTY4LjgyNGMwLjQ4OS00LjU0MywwLjc0Ny05LjE1NCwwLjc0Ny0xMy44MjRzLTAuMjU4LTkuMjgxLTAuNzQ3LTEzLjgyNGwxOC4xNjQtMTAuNDg3CgljMy44ODEtMi4yNDEsNi42NTgtNS44Niw3LjgxNi0xMC4xOTFjMS4xNi00LjMzLDAuNTY0LTguODU0LTEuNjc2LTEyLjczNWwtMzQuOTQzLTYwLjUyNGMtMi45OS01LjE4LTguNTY0LTguMzk2LTE0LjU1MS04LjM5NgoJYy0yLjkzLDAtNS44MjYsMC43NzgtOC4zNzcsMi4yNTFMMjMwLjYxOSw1MS42MWMtNy40MDItNS40MjktMTUuNDA2LTEwLjA4My0yMy44OTMtMTMuODQyVjE2Ljc4MwoJQzIwNi43MjcsNy41MjksMTk5LjE5NSwwLDE4OS45NDUsMGgtNjkuODkxYy05LjI1NCwwLTE2Ljc4MSw3LjUyOS0xNi43ODEsMTYuNzgzdjIwLjk4NWMtOC40ODYsMy43NTktMTYuNDksOC40MTMtMjMuODk0LDEzLjg0MgoJTDYxLjE2NCw0MS4wOTRjLTIuNTUxLTEuNDczLTUuNDQ1LTIuMjUtOC4zNzUtMi4yNWMtNS45ODYsMC0xMS41NjMsMy4yMTUtMTQuNTUzLDguMzk1TDMuMjk1LDEwNy43NjIKCWMtMi4yNDIsMy44ODEtMi44MzYsOC40MDYtMS42NzQsMTIuNzM2YzEuMTU2LDQuMzMsMy45MzUsNy45NDksNy44MTQsMTAuMTkxTDI3LjYsMTQxLjE3NmMtMC40ODksNC41NDMtMC43NDcsOS4xNTQtMC43NDcsMTMuODI0CglzMC4yNTgsOS4yODEsMC43NDcsMTMuODI0TDkuNDM1LDE3OS4zMTFjLTMuODc5LDIuMjQxLTYuNjU4LDUuODYtNy44MTQsMTAuMTkxYy0xLjE2Miw0LjMzLTAuNTY4LDguODU1LDEuNjc0LDEyLjczNWwzNC45NDEsNjAuNTI0CgljMi45OSw1LjE4LDguNTY2LDguMzk1LDE0LjU1Myw4LjM5NWMyLjkzLDAsNS44MjQtMC43NzcsOC4zNzUtMi4yNUw3OS4zOCwyNTguMzljNy40MDMsNS40MjksMTUuNDA3LDEwLjA4MywyMy44OTQsMTMuODQydjIwLjk4NgoJYzAsNC40ODIsMS43NDQsOC42OTUsNC45MTQsMTEuODY2YzMuMTc0LDMuMTY5LDcuMzg1LDQuOTE2LDExLjg2Nyw0LjkxNmg2OS44OTFjOS4yNSwwLDE2Ljc4MS03LjUyOSwxNi43ODEtMTYuNzgydi0yMC45ODYKCWM4LjQ4Ni0zLjc1OSwxNi40OS04LjQxMywyMy44OTMtMTMuODQybDE4LjIxNSwxMC41MTdjMi41NTEsMS40NzMsNS40NDcsMi4yNSw4LjM3NywyLjI1YzUuOTg2LDAsMTEuNTYxLTMuMjE1LDE0LjU1MS04LjM5NQoJbDM0Ljk0My02MC41MjNjMi4yNC0zLjg4MSwyLjgzNi04LjQwNiwxLjY3Ni0xMi43MzZDMzA3LjIyMywxODUuMTcyLDMwNC40NDUsMTgxLjU1MywzMDAuNTY0LDE3OS4zMTF6IE0xNTUsMjQ2LjEwMQoJYy0xOC4yMywwLTM1LjIwNy01LjM1Ny00OS40NDktMTQuNTc5bDMwLjgwMS0zMC44MDRjNi40NDksMi43NzIsMTMuNDUsNC4yNCwyMC42NzcsNC4yNDFjMC4wMDIsMCwwLjAwMywwLDAuMDA0LDAKCWMxNC4wMTEsMCwyNy4xNzUtNS40NjYsMzcuMDY0LTE1LjM5YzEzLjUtMTMuNTM2LDE4LjU0MS0zMy4zNjMsMTMuMTU1LTUxLjc0M2MtMC4zMTMtMS4wNjktMS4xNjgtMS44OTQtMi4yNDgtMi4xNjkKCWMtMS4wNzgtMC4yNzctMi4yMjUsMC4wNC0zLjAxMSwwLjgyOWwtMzIuOTcsMzMuMDY5Yy0xLjk3OS0wLjgwNC02Ljk2MS0zLjU0Mi0xNi4xODYtMTIuNzM2CgljLTkuMjI2LTkuMTk3LTExLjk3Ni0xNC4xNzMtMTIuNzgzLTE2LjE0OGwzMi45NjYtMzMuMDY5YzAuNzg3LTAuNzg5LDEuMDk4LTEuOTM1LDAuODItMy4wMTNjLTAuMjc4LTEuMDc5LTEuMTA1LTEuOTMxLTIuMTc1LTIuMjQxCgljLTQuNzUxLTEuMzc4LTkuNjc2LTIuMDc4LTE0LjYzNy0yLjA3OGMtMTQuMDE2LDAtMjcuMTgxLDUuNDY0LTM3LjA2OSwxNS4zODVjLTkuODczLDkuOTAzLTE1LjI5OSwyMy4wNTctMTUuMjgsMzcuMDM5CgljMC4wMSw3LjIwNCwxLjQ3NiwxNC4xOCw0LjI0LDIwLjYwNGwtMzAuNzIyLDMwLjcyNEM2OS4xNDUsMTg5Ljg2OCw2My44OTYsMTczLjA0Nyw2My44OTYsMTU1CgljMC01MC4zMTMsNDAuNzg3LTkxLjEwMiw5MS4xMDQtOTEuMTAyczkxLjEwMiw0MC43ODksOTEuMTAyLDkxLjEwMlMyMDUuMzE2LDI0Ni4xMDEsMTU1LDI0Ni4xMDF6Ii8+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+Cjwvc3ZnPg==");
map.put("theme", "dark");
map.put("pluginType", 1);
map.put("isNeedConfigured", 1);
map.put("configAttrs", "[{\"attrCode\":\"esHost\",\"attrLabel\":\"ES服务器\"},{\"attrCode\":\"esPort\",\"attrLabel\":\"端口\"}]");
return map;
}
}
```
### 当前平台支持的扩展参数
| 参数名称 | 类型 | 是否必填 | 描述 |
|------------------|---------|------|------------------------------------|
| logo | String | 是 | 必须是svg格式传值内容必须是svg代码的Base64编码 |
| pluginType | Integer | 否 | 插件类型 |
| isNeedConfigured | Integer | 否 | 是否需要在主程序填写配置信息默认01为需要 |
| configAttrs | List | 否 | 配置属性集合json字符串。具体内容见下 |
| theme | String | 否 | 插件主题可选项dark深色light浅色默认light |
**configAttrs单个元素**
| 参数名称 | 类型 | 是否必填 | 描述 |
|-----------|--------|------|--------|
| attrLabel | String | 是 | 属性标签 |
| attrCode | String | 是 | 属性Code |
## Maven打包配置
### 插件信息配置
主要在 `configuration`节点下进行详细配置,说明如下:
#### mode 【必配】
当前节点主要是设置将插件打成开发/生产模式运行的插件。
```XML
<mode>prod</mode>
```
| 参数名称 | 类型 | 是否必填 | 描述 |
|------|--------|------|------------------------|
| mode | String | 是 | 打包模式。可选参数:`dev`、`prod` |
#### pluginInfo【必配】
当前节点主要是定义插件信息.
```XML
<pluginInfo>
<id>email-notice</id>
<bootstrapClass>org.opengauss.admin.plugin.EmailNoticeApplication</bootstrapClass>
<version>1.0.0</version>
<provider>xielibo</provider>
<description>邮件通知插件</description>
</pluginInfo>
```
**注意插件id和版本号中不能出现** **`@`****、** **`,`****特殊符号**
| 参数名称 | 类型 | 是否必填 | 描述 |
|----------------|--------|------|-----------------------|
| id | String | 是 | 定义插件全局唯一id |
| bootstrapClass | String | 是 | 插件引导类包名 |
| version | String | 是 | 插件版本号。版本号要求见如下`版本号规则` |
| provider | String | 是 | 插件提供开发者名称 |
| description | String | 是 | 插件描述信息 |
#### 完整样例
```XML
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>com.gitee.starblues</groupId>
<artifactId>spring-brick-maven-packager</artifactId>
<version>3.1.0</version>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>com.gitee.starblues</groupId>
<artifactId>spring-brick-maven-packager</artifactId>
<configuration>
<mode>prod</mode>
<pluginInfo>
<id>email-notice</id>
<bootstrapClass>org.opengauss.admin.plugin.EmailNoticeApplication</bootstrapClass>
<version>1.0.0</version>
<provider>xielibo</provider>
<description>邮件通知插件</description>
</pluginInfo>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<!--如果是功能插件,需要增加一下配置,进行前端资源的打包构建-->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>2.5</version>
<executions>
<execution>
<id>copy-web</id>
<phase>generate-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${basedir}/src/main/resources</outputDirectory>
<resources>
<resource>
<directory>工程中前端构建后的文件目录</directory>
<targetPath>后端中存放前端资源的目录</targetPath>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
```
### 编译插件
执行maven命令`mvn clean package`进行编译/打包
## **插件中注入主程序Bean**
### 方式1: 直接注入式
可直接通过`@Autowired`或 `@Resource`注解进行注入,支持`属性注解`、`构造器`、`set方法`注入。
为了用户可自主控制注入,框架中提供了额外注解`@AutowiredType`进行控制注入类型:
- `@AutowiredType(AutowiredType.Type.PLUGIN)`: **【默认类型】**直接从插件中进行注入。
- `@AutowiredType(AutowiredType.Type.MAIN)`: 直接从主程序进行注入。
- `@AutowiredType(AutowiredType.Type.PLUGIN_MAIN)`: 优先从插件中注入,如果不存在, 则从主程序进行注入。
- `@AutowiredType(AutowiredType.Type.MAIN_PLUGIN)`: 优先从主程序中注入,如果不存在,则从插件中进行注入。
**注入案例**
```Java
@Service
public class BasicService {
@Autowired
@AutowiredType(AutowiredType.Type.PLUGIN_MAIN)
private MenuFacade menuFacade;
}
```
### 方式2: 全局配置式
在插件中可通过如下方式进行全局配置注入类型。使用步骤如下:
1. 实现`AutowiredTypeDefiner`对象, 定义全局注入配置。
```Java
public class AutowiredTypeDefinerImpl implements AutowiredTypeDefiner {
@Override
public void config(AutowiredTypeDefinerConfig config) {
config
.add(AutowiredType.Type.MAIN, "org.opengauss.admin.system.plugin.facade.MenuFacade")
.add(AutowiredType.Type.MAIN, DataSource.class);
}
}
```
1. 在插件引导类重写`autowiredTypeDefiner`方法,返回`AutowiredTypeDefiner`实现对象。
```Java
@SpringBootApplication
public class Basic1Plugin extends SpringPluginBootstrap {
@Override
protected AutowiredTypeDefiner autowiredTypeDefiner() {
return new AutowiredTypeDefinerImpl();
}
}
```
### 方式3: 代码调用式
通过代码直接从主程序获取`Bean`, 使用方式如下:
直接注入`MainApplicationContext`,调用其`getSpringBeanFactory()`方法获取`SpringBeanFactory`,然后获取主程序中`Bean`
```Java
@Service
public class BasicService {
@Autowired
private MainApplicationContext context;
public void getName() {
SpringBeanFactory factory = context.getSpringBeanFactory();
MenuFacade menuFacade = factory.getBean(MenuFacade.class);
}
}
```

View File

@ -0,0 +1,454 @@
**openGauss DataKit Platform产品使用手册**
# **文档历史**
| **修订日期** | **修订内容** | **版本号** | **编写人** |
| --- | --- | --- | --- |
| 2022/12/18 | 初稿 | 1.0 | 张绍鹏 |
| 2022/12/21 | 内部评审后修改 | 1.1 | 张绍鹏 |
| 2023/1/13 | 按要求改为md格式 | 1.2 | 张绍鹏 |
# **关于本手册**
## **产品介绍**
openGauss的安装、运维场景对于初级用户或单纯想要测试openGauss数据库基本特性的使用者来说技术难度较大、过程较为复杂、学习曲线较为陡峭尤其企业版安装对一般用户来说操作难度很大。使用可视化运维平台可以屏蔽openGauss的技术细节让普通用户能够快速上手体验功能让运维人员能够快速在企业环境中部署、卸载各类openGauss集群减少了用户的学习成本和运维成本实现了对openGauss各种常见操作的可视化屏蔽了各种不同openGauss版本中的运维命令差异可以让用户使用相同的方式操作数据库不用知道命令细节也可以使用openGauss数据库的各项能力让用户可以专注于自身的业务领域。
因此需要开发一些有针对性的运维监控工具为不同配置不同运维要求的客户提供运维技术支撑这些都将是openGauss社区的宝贵资产。而社区急需一个一体化的平台通过插件的方式将这些工具进行整合并支持方便快捷的个性化配置。对于社区合作伙伴已经开发的Web工具合作伙伴也可以根据《插件开发指南》的要求自行改造为插件整合到一体化平台中。
本产品是基于Web的openGauss的可视化的一体化平台系统目的是方便客户使用和管理openGauss可视化工具可以为客户降低openGauss数据库安装使用门槛做到安全中心管理插件管理以及其它功能包括一键化部署、卸载、组件化安装、多版本升级、日常运维和。本文档主要适用openGauss3.0.0+版本。
## **主要功能介绍**
本产品主要包含以下几个功能特性:
- 插件平台(详见平台开发文档,该手册不再赘述)
- 用于插件开发的前后端一体的开发框架(详见插件开发文档,该手册不再赘述)
- Mysql数据迁移工具插件实现
- 安全中心(账号管理、角色权限管理和白名单管理)
- 插件管理模块
- 日志中心,包括操作日志和系统日志(日志级别/切割规则/保留天数的设置和日志下载)
- 国际化(中/英)
- 主题切换(黑/白)
- 基础模块(用户中心、修改个人密码、登录/登出)
## **预期读者**
- 用户
- 测试人员
- 开发人员
## **名词解释**
| **名词** | **解释** | **备注** |
| ---------- | -------------------------- | -------------------------------------------------- |
| 超级管理员 | 系统安装后即可用的内置账号 | 该账号不可删除,而且是唯一可以访问“安全中心”的账号 |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
##
## **版本及功能列表**
| **版本号** | **新增功能** | **发布时间** |
| --- | --- | --- |
| V1.0 | 首次发布 | 2022-12-18 |
## **平台基础模块(登录/登出/用户中心/密码)**
### **概述**
平台基础模块包括登录平台,登出平台,修改密码,用户中心。该工具是一个本地(内网)工具,不是面向公网开放的平台,所以没有注册用户的需求,但是有用户管理能力,超级管理员可在平台内部新增用户来达到用户管理的能力。
### **角色**
所有用户
### **界面**
1、登录平台
![platform-login](./_resources/platform-login.png)
2、退出登录
![platform-logout](./_resources/platform-logout.png)
3、修改密码
![platform-change-password-menu](./_resources/platform-change-password-menu.png)
<img src="./_resources/platform-user-change-password.png" alt="platform-user-change-password" style="zoom:50%;" />
4、用户中心
点击用户中心
![platform-user-center-menu](./_resources/platform-user-center-menu.png)
点击用户名旁边的“编辑”图标
![platform-user-center-view](./_resources/platform-user-center-view.png)
修改用户信息,包括:
- 账号名
- 用户昵称
- 手机号码(系统会检查唯一性)
- 邮箱optional
![platform-edit-user-info](./_resources/platform-edit-user-info.png)
点击更换头像弹窗选择图片上传Upload success后显示出头像
![platform-upload-avatar](./_resources/platform-upload-avatar.png)
![platform-new-avatar](./_resources/platform-new-avatar.png)
## **国际化和黑白主题**
### **概述**
本平台支持国际化热切换(中文和英文),支持黑白主题切换。
### **角色**
所有用户
### **界面**
1. 切换语言
![platform-change-language](./_resources/platform-change-language.png)
**切换到英文后的效果:**
![platform-English](./_resources/platform-English.png)
1. 切换主题
**点击右上角的太阳/月亮图标即可切换主题:**
<img src="./_resources/platform-theme-light.png" alt="platform-theme-light" style="zoom:67%;" />
点击右上角太阳/月亮按钮进行黑白主题toggle切换
![platform-theme-dark](./_resources/platform-theme-dark.png)
黑色主题的效果:
![platform-theme-darkmode](./_resources/platform-theme-darkmode.png)
## **插件管理页面**
### **概述**
在本页面可以完成对插件的集中管理,包括以下操作:插件一览,安装插件,卸载插件,停用插件,启用插件等。
### **角色**
拥有插件管理权限的角色可以访问
### **界面**
1、插件一览
![platform-plugin-mgmt](./_resources/platform-plugin-mgmt.png)
**说明:**
每个插件包括以下信息:
- 图标
- 名称
- 版本
- 开发者
- 简介
2、安装插件
![platform-plugin-upload-jar](./_resources/platform-plugin-upload-jar.png)
**说明:**
- 在插件管理页面,点击右上角“安装插件”按钮
- 将插件jar包拖拽到窗口里 或 打开文件选择器选择插件jar包
- 点击“播放”按钮进行安装
- 安装成功后:插件一览列表会出现对应的插件信息卡片,左侧系统菜单会出现插件相关的菜单项。
3、卸载插件
为了提供更灵活的菜单配置能力,平台允许插件创建二级菜单。又为了让同类插件的菜单可以归类于一级菜单下,平台也允许插件在其它插件菜单下创建二级菜单,这样就会存在插件菜单间的依赖,所以在卸载插件时要注意:
*如果其它插件在你的插件菜单下创建了子菜单项,那你需要先卸载那个插件消除依赖后才能卸载你的插件。*
插件被卸载后该插件下的所有Tab标签页都会被自动关闭。
## **安全中心**
### **概述**
安全中心提供登录权限和账号管理,以及访问白名单管理。
### **角色**
出于安全性设计的考量平台只允许超级管理员即安装后就有的系统内置账号可以访问安全中心。其它Role配置权限时甚至看不到安全中心的菜单所以也无法给其它Role配置安全中心的权限。
### **界面**
1、白名单管理
![platform-security-whitelist](./_resources/platform-security-whitelist.png)
<img src="./_resources/platform-security-whitelist-add.png" alt="platform-security-whitelist-add" style="zoom:70%;" />
**说明:**
- 左侧菜单栏找到“安全中心-访问白名单”菜单,点击打开“白名单管理”页面;
- 点击右上角“添加白名单”按钮
- 在弹出框中填写白名单名称和允许访问的IP地址多个IP地址间用逗号隔开确认即可完成添加。
2、角色与权限菜单
本系统支持通过创建特定的权限组合来管理用户权限,权限组合被定义为“角色”,一个用户只能拥有一个角色。
![platform-security-role-list](./_resources/platform-security-role-list.png)
点击“添加角色”
创建或编辑“角色”时,可以为“角色”配置权限范围,包括已经安装的插件的访问权限。
*注意:出于安全性设计的考虑,任何添加的”角色“都无法拥有”安全中心“的访问权限,”安全中心“的权限被固定在”超级管理员“身上。*
<img src="./_resources/platform-security-add-role.png" alt="platform-security-add-role" style="zoom:67%;" />
**说明:**
- 左侧菜单栏找到“安全中心-角色菜单”,点击打开“角色菜单”管理页面;
- 点击右上角“添加角色”按钮
- 在弹出框中填写角色名称,并勾选允许访问的菜单项,可以选择多个菜单项,可以全选,可以添加备注,确认即可完成添加。
3、账号与权限
![platform-security-account-list](./_resources/platform-security-account-list.png)
<img src="./_resources/platform-security-add-user.png" alt="platform-security-add-user" style="zoom:67%;" />
**说明:**
- 左侧菜单栏找到“安全中心-账号与权限”,点击打开“账号与权限”管理页面;
- 点击右上角“添加用户”按钮
- 在弹出框中填写以下字段:
- 账号:登录用户名
- 密码:登录密码
- 昵称:
- 手机号码:唯一手机号
- 角色:不选择角色则没有任何权限
- 状态:启用或停用
- 备注:可选填
点击确定完成添加账号。
![platform-security-reset-password-dialog](./_resources/platform-security-reset-password-dialog.png)
点击“重置密码”可以为某个用户重置密码
## **日志中心**
### **概述**
日志中心提供系统日志管理和操作日志管理功能。
### **角色**
拥有日志中心访问权限的账号均可以访问该功能。
### **界面**
1、系统日志
![platform-logs-system-list](./_resources/platform-logs-system-list.png)
<img src="./_resources/platform-logs-system-configuration.png" alt="platform-logs-system-configuration" style="zoom:67%;" />
**说明:**
- 左侧菜单栏找到“日志中心-系统日志”,点击打开“系统日志”管理页面;
- 可以通过搜索找到对应的日志文件,点击下载日志文件
- 点击右上角“日志设置”按钮
- 在弹出框中进行系统日志配置:
- 日志输出级别四个级别DEBUGINFOWARNERROR
- 保留天数:输入天数,操作天数的日志会被自动清除
- 单个日志文件大小:单个日志文件达到这个限制会自动创建新的日志文件
- 最大占用空间:所有日志文件被允许占用的最大存储空间
2. 操作日志
![platform-logs-operation-list](./_resources/platform-logs-operation-list.png)
<img src="./_resources/platform-logs-operation-details.png" alt="platform-logs-operation-details" style="zoom:67%;" />
**说明:**
1左侧菜单栏找到“日志中心-操作日志”,点击打开“操作日志”管理页面;
2可以通过搜索找到对应的日志文件
3点击“详情”查看该操作的详细参数
## 设备管理
用户通过【资源中心】-【设备管理】进入本功能画面在这个画面上左侧为物理机基本信息右侧为实时监控和操作面板监控指标包括网络速率、CPU使用率、内存使用率、磁盘使用率。用户可以根据ip、操作系统、标签对设备进行筛选。在root密码没有选择保存的情况下实时数据将无法获取。
![](./_resources/equipment-manage.png)
点击【创建】按钮可以对物理机进行创建在弹出的对话框中用户需要输入内网IP、外网IP、ssh端口号、root密码等创建物理机。点击标签下拉框可以选择标签也可以输入字符来创建标签。点击连通性测试可以测试到设备的ssh连接是否正常。
![](./_resources/equipment-manage-create.png)
点击操作面板的【编辑】可以对物理机进行编辑包括ip、标签、密码等的修改。
![](./_resources/equipment-manage-edit.png)
点击【标签设置】,可以对勾选的多个设备批量设置标签。
![](./_resources/equipment-manage-tag-batch.png)
点击【标签管理】,可以展示当前所有的标签和关联的主机数量,用户可以在这里对标签做统一的修改或者删除。
![](./_resources/equipment-manage-tag-manage.png)
在需要大批量导入的场景中,用户可以点击【批量导入】按钮,在弹出的对话框中点击【导入模板下载】下载导入模板。
![](./_resources/server-manage-template-download.png)
打开下载好的模板文件输入对应的序号、服务器名称、内网IP等信息。
![](./_resources/server-manage-template.png)
点击【选择文件】将需要导入的文件上传,然后点击【确定】开始解析。
![](./_resources/server-manage-parsing-files.png)
等待解析完成,完成后会出现解析结果,如果导入数据有误,点击【下载错误报告按钮】下载错误报告查看具体原因。
![](./_resources/server-manage-import-completed.png)
打开错误报告文件会显示导入失败的记录并显示其失败原因。
![](./_resources/server-manage-error-reporting.png)
## 实例管理
用户通过【资源中心】-【实例管理】进入本功能画面,在这个画面上,分为节点信息和集群信息两级展示,集群信息一层展示集群名称、数据库类型、运行状态等基本信息,节点一层展示节点所在主机的基本信息、角色、运行状态、实时监控数据等。
用户可以在这个画面上根据集群名称、ip地址、数据库类型对实例进行过滤搜索。
![](./_resources/jdbc-manage.png)
点击【创建】后,用户在新增对话框内可以输入对应数据库节点的连接信息和扩展属性,并可以测试连通性。
在不勾选自定义名称的情况下将以前两个节点的IP地址和端口进行组合作为集群名称。
![](./_resources/jdbc-manage-create.png)
在需要大批量导入的场景中,用户可以先点击【下载模板】下载导入模板
然后点击【点击上传】进行导入,导入中出现的问题将提示用户进行修改。
## 安装包管理
【安装包管理】支持【新增安装包】、【批量检查】、【批量删除】。
![](./_resources/package-management.png)
【新增安装包】支持openEluer20.03、openEluer22.03、centOs7三个操作系统其余选项根据机器类型进行选择下载链接自动关联点击确定即可下载成功。
![](./_resources/package-management-add.png)
下载成功后,点击【批量检查】或者【检查】可检查安装包是否下载成功、点击【更新】可替换安装包。
![](./_resources/package-management-other.png)
【更新安装包】界面(点击【离线上传】进行替换安装包)
![](./_resources/package-management-update.png)
## 集群管理
### 集群列表
【集群列表】展示已安装或者已导入datakit中的集群有启停、卸载、备份数据、删除等功能。
![](./_resources/cluster-manage-list.png)
### 并行安装任务
#### 草稿箱
【草稿箱】中的集群任务通过【创建并行安装任务】创建、创建成功后如果【环境监测】成功,则可以进行【发布】,【发布】后进入【任务列表】界面进行安装。
![](./_resources/cluster-manage-task-draft.png)
![](./_resources/cluster-manage-task-draft-publish.png)
点击【复制】可以快速复制操作系统、架构、安装包相同的安装任务、删除支持【批量删除】和单个【删除】、【编辑】可以修改任务的配置、【执行日志下载】可以下载日志查看“创建”-“执行成功”的流程
![](./_resources/cluster-manage-task-draft-other.png)
点击【任务ID】可以查看【任务详情】点击【编辑】进行修改。
![](./_resources/cluster-manage-task-look.png)
![](./_resources/cluster-manage-task-detail.png)
#### 任务列表
任务列表界面展示通过环境检查的安装任务、可以点击【一键执行】或者【批量执行】进行安装。
![](./_resources/cluster-manage-tasks-list-execute.png)
## **系统设置**
### **概述**
系统设置可以让用户设置整个Datakit平台上的全局配置。
### **角色**
拥有系统设置权限的账号均可以访问该功能。
### **界面**
用户通过点击右上角【系统设置】菜单进入该功能。
![](./_resources/sys-setting-enter.jpg)
在这个画面上包含文件上传路径、迁移套件在线下载地址、迁移套件安装包名称、迁移套件jar包名称四个设置项。
![](./_resources/sys-setting.png)
其中文件上传路径指的是所有通过Datakit页面上传文件的存放路径该路径必须以/结尾,并且不能与其它用户使用同一个文件夹以防止文件互相覆盖。
文件上传路径的初始默认值位于基座工程的sql文件中路径为visualtool-api/src/main/resources/db/openGauss-visualtool.sql。
如需更改文件上传路径的默认值/ops/files可以修改sys_setting表中的初始数据为其它路径。
![](./_resources/sys-setting-file-path-sql.jpg)
迁移相关的三个设置迁移套件在线下载地址、迁移套件安装包名称、迁移套件jar包名称用于数据迁移在线安装迁移套件时作为默认值。
其中在线下载地址指的是下载迁移套件的路径迁移套件jar名称指的解压缩安装包后启动目标文件的名称。

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Some files were not shown because too many files have changed in this diff Show More