mirror of
https://github.com/dromara/hertzbeat.git
synced 2026-09-17 18:19:02 +00:00
Compare commits
48
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0c6933a88 | ||
|
|
3e567e5be6 | ||
|
|
a995dd7daf | ||
|
|
4ae6ba9a3c | ||
|
|
360abcc61d | ||
|
|
c8739f530e | ||
|
|
7b2c822802 | ||
|
|
339c9775e3 | ||
|
|
8efbba1693 | ||
|
|
20e016dff7 | ||
|
|
f5bd371114 | ||
|
|
172a279082 | ||
|
|
f4d19a2f7f | ||
|
|
47f2d5be6c | ||
|
|
c48abad4cf | ||
|
|
6c6b253f00 | ||
|
|
6878bf84ae | ||
|
|
926a399105 | ||
|
|
29e1e9cb40 | ||
|
|
1397bb15ec | ||
|
|
8a9765c99a | ||
|
|
6f935357b8 | ||
|
|
9faead5aee | ||
|
|
a56aa09ed6 | ||
|
|
41e57db032 | ||
|
|
b62137d185 | ||
|
|
2bd635e1d4 | ||
|
|
8e8ab80aa0 | ||
|
|
7f4c2e0487 | ||
|
|
e52e715a53 | ||
|
|
e3e7377d86 | ||
|
|
625dbe9624 | ||
|
|
42f0a74edf | ||
|
|
cc9f53d71a | ||
|
|
2f122dfb05 | ||
|
|
f7b0010180 | ||
|
|
2c0fecaa62 | ||
|
|
548966906c | ||
|
|
6473546f56 | ||
|
|
035d63c880 | ||
|
|
d56176dfa5 | ||
|
|
c1815f5107 | ||
|
|
7ac175e726 | ||
|
|
eb5e843251 | ||
|
|
bc098aa4d2 | ||
|
|
bcd394b47f | ||
|
|
fd2202fcaf | ||
|
|
a7629e317a |
+10
-10
@@ -90,24 +90,24 @@ e2e:
|
||||
backend:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "alerter/**/*"
|
||||
- "collector/**/*"
|
||||
- "common/**/*"
|
||||
- "grafana/**/*"
|
||||
- "manager/**/*"
|
||||
- "push/**/*"
|
||||
- "remoting/**/*"
|
||||
- "warehouse/**/*"
|
||||
- "hertzbeat-alerter/**/*"
|
||||
- "hertzbeat-collector/**/*"
|
||||
- "hertzbeat-common/**/*"
|
||||
- "hertzbeat-grafana/**/*"
|
||||
- "hertzbeat-manager/**/*"
|
||||
- "hertzbeat-push/**/*"
|
||||
- "hertzbeat-remoting/**/*"
|
||||
- "hertzbeat-warehouse/**/*"
|
||||
|
||||
collector:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "collector/**/*"
|
||||
- "hertzbeat-collector/**/*"
|
||||
|
||||
monitoring-template:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "manager/src/main/resources/define/**/*"
|
||||
- "hertzbeat-manager/src/main/resources/define/**/*"
|
||||
|
||||
HIP:
|
||||
- changed-files:
|
||||
|
||||
+3
-3
@@ -4,7 +4,7 @@ image:
|
||||
tasks:
|
||||
|
||||
- name: Run backend
|
||||
before: cd hertzbeat-manager
|
||||
before: cd hertzbeat-startup
|
||||
command: |
|
||||
gp sync-await setup-backend
|
||||
mvn spring-boot:run
|
||||
@@ -13,7 +13,7 @@ tasks:
|
||||
before: cd web-app
|
||||
command: |
|
||||
gp sync-await setup-frontend
|
||||
yarn start --public-host "`gp url 4200`"
|
||||
pnpm start --public-host "`gp url 4200`"
|
||||
openMode: split-right
|
||||
|
||||
- name: Setup backend
|
||||
@@ -25,7 +25,7 @@ tasks:
|
||||
|
||||
- name: Setup frontend
|
||||
init: |
|
||||
cd web-app && yarn install
|
||||
cd web-app && pnpm install
|
||||
command: |
|
||||
gp sync-done setup-frontend
|
||||
exit 0
|
||||
|
||||
+8
-30
@@ -20,27 +20,27 @@
|
||||
|
||||
- 💬**Suggestion**: You can also make some suggestions to the project or community to promote the healthy development of the community;
|
||||
|
||||
More see [Contribution Types](https://allcontributors.org/docs/en/emoji-key)
|
||||
More see [Contribution Types](https://allcontributors.org/emoji-key)
|
||||
|
||||
Even small corrections to typos are very welcome :)
|
||||
|
||||
### Getting HertzBeat up and running
|
||||
|
||||
> To get HertzBeat code running on your development tools, and able to debug with breakpoints.
|
||||
> This is a front-end and back-end separation project. To start the local code, the back-end [manager](manager) and the front-end [web-app](web-app) must be started separately.
|
||||
> This is a front-end and back-end separation project. To start the local code, the back-end [startup](hertzbeat-startup) and the front-end [web-app](web-app) must be started separately.
|
||||
|
||||
|
||||
#### Backend start
|
||||
|
||||
1. Requires `maven3+`, `java17` and `lombok` environments
|
||||
|
||||
2. (Optional) Modify the configuration file: `manager/src/main/resources/application.yml`
|
||||
2. (Optional) Modify the configuration file: `hertzbeat-startup/src/main/resources/application.yml`
|
||||
|
||||
3. Execute under the project root directory: `mvn clean install -DskipTests`
|
||||
|
||||
4. Add VM Options: `--add-opens=java.base/java.nio=org.apache.arrow.memory.core,ALL-UNNAMED`
|
||||
|
||||
5. Start `springboot manager` service: `manager/src/main/java/org/apache/hertzbeat/hertzbeat-manager/Manager.java`
|
||||
5. Start `springboot startup` service: `hertzbeat-startup/src/main/java/org/apache/hertzbeat/startup/HertzBeatApplication.java`
|
||||
|
||||
#### Frontend start
|
||||
|
||||
@@ -131,17 +131,6 @@ Add WeChat account `ahertzbeat` to pull you into the WeChat group.
|
||||
|
||||
## 🥐 Architecture
|
||||
|
||||
- **[manager](https://github.com/apache/hertzbeat/tree/master/hertzbeat-manager)** Provide monitoring management, system management basic services.
|
||||
> Provides monitoring management, monitoring configuration management, system user management, etc.
|
||||
- **[collector](https://github.com/apache/hertzbeat/tree/master/collector)** Provide metrics data collection services.
|
||||
> Use common protocols to remotely collect and obtain peer-to-peer metrics data.
|
||||
- **[warehouse](https://github.com/apache/hertzbeat/tree/master/warehouse)** Provide monitoring data warehousing services.
|
||||
> Metrics data management, data query, calculation and statistics.
|
||||
- **[alerter](https://github.com/apache/hertzbeat/tree/master/hertzbeat-alerter)** Provide alert service.
|
||||
> Alarm calculation trigger, monitoring status linkage, alarm configuration, and alarm notification.
|
||||
- **[web-app](https://github.com/apache/hertzbeat/tree/master/web-app)** Provide web ui.
|
||||
> Angular Web UI.
|
||||
|
||||

|
||||
|
||||
<br>
|
||||
@@ -172,26 +161,26 @@ Add WeChat account `ahertzbeat` to pull you into the WeChat group.
|
||||
|
||||
- 💬**建议**:也可以对项目或者社区提出一些建议,促进社区的良性发展;
|
||||
|
||||
更多贡献方式参见 [Contribution Types](https://allcontributors.org/docs/en/emoji-key)
|
||||
更多贡献方式参见 [Contribution Types](https://allcontributors.org/emoji-key)
|
||||
|
||||
即便是小到错别字的修正我们也都非常欢迎 :)
|
||||
|
||||
### 让 HertzBeat 运行起来
|
||||
|
||||
> 让 HertzBeat 的代码在您的开发工具上运行起来,并且能够断点调试。
|
||||
> 此为前后端分离项目,本地代码启动需将后端[manager](manager)和前端[web-app](web-app)分别启动生效。
|
||||
> 此为前后端分离项目,本地代码启动需将后端[hertzbeat-startup](hertzbeat-startup)和前端[web-app](web-app)分别启动生效。
|
||||
|
||||
#### 后端启动
|
||||
|
||||
1. 需要 `maven3+`, `java17` 和 `lombok` 环境
|
||||
|
||||
2. (可选)修改配置文件配置信息-`manager/src/main/resources/application.yml`
|
||||
2. (可选)修改配置文件配置信息-`hertzbeat-startup/src/main/resources/application.yml`
|
||||
|
||||
3. 在项目根目录运行编译: `mvn clean install -DskipTests`
|
||||
|
||||
4. 在 `jvm` 加入参数 `--add-opens=java.base/java.nio=org.apache.arrow.memory.core,ALL-UNNAMED`
|
||||
|
||||
5. 启动`springboot manager`服务 `manager/src/main/java/org/apache/hertzbeat/hertzbeat-manager/Manager.java`
|
||||
5. 启动`springboot startup`服务 `hertzbeat-startup/src/main/java/org/apache/hertzbeat/startup/HertzBeatApplication.java`
|
||||
|
||||
|
||||
#### 前端启动
|
||||
@@ -282,15 +271,4 @@ Add WeChat account `ahertzbeat` to pull you into the WeChat group.
|
||||
|
||||
### 模块
|
||||
|
||||
- **[manager](https://github.com/apache/hertzbeat/tree/master/hertzbeat-manager)** 提供监控管理,系统管理基础服务
|
||||
> 提供对监控的管理,监控应用配置的管理,系统用户租户后台管理等。
|
||||
- **[collector](https://github.com/apache/hertzbeat/tree/master/collector)** 提供监控数据采集服务
|
||||
> 使用通用协议远程采集获取对端指标数据。
|
||||
- **[warehouse](https://github.com/apache/hertzbeat/tree/master/warehouse)** 提供监控数据仓储服务
|
||||
> 采集指标结果数据管理,数据落盘,查询,计算统计。
|
||||
- **[alerter](https://github.com/apache/hertzbeat/tree/master/hertzbeat-alerter)** 提供告警服务
|
||||
> 告警计算触发,任务状态联动,告警配置,告警通知。
|
||||
- **[web-app](https://github.com/apache/hertzbeat/tree/master/web-app)** 提供可视化控制台页面
|
||||
> 监控告警系统可视化控制台前端
|
||||
|
||||

|
||||
|
||||
@@ -155,8 +155,8 @@ Detailed config refer to [Install HertzBeat via Package](https://hertzbeat.apach
|
||||
|
||||
##### 3:Start via source code
|
||||
|
||||
1. Local source code debugging needs to start the back-end project `manager` and the front-end project `web-app`.
|
||||
2. Backend:need `maven3+`, `java17`, `lombok`, add VM options in IDE: ` --add-opens=java.base/java.nio=org.apache.arrow.memory.core,ALL-UNNAMED `, then start the `manager` service.
|
||||
1. Local source code debugging needs to start the back-end project `hertzbeat-startup` and the front-end project `web-app`.
|
||||
2. Backend:need `maven3+`, `java17`, `lombok`, add VM options in IDE: ` --add-opens=java.base/java.nio=org.apache.arrow.memory.core,ALL-UNNAMED `, then start the `hertzbeat-startup` service.
|
||||
3. Web:need `nodejs npm angular-cli` environment, Run `ng serve --open` in `web-app` directory after backend startup.
|
||||
4. Access `http://localhost:4200` to start, default account: `admin/hertzbeat`
|
||||
|
||||
|
||||
+2
-2
@@ -152,8 +152,8 @@
|
||||
|
||||
##### 方式三:本地代码启动
|
||||
|
||||
1. 此为前后端分离项目,本地代码调试需要分别启动后端工程 `manager` 和前端工程 `web-app`
|
||||
2. 后端:需要 `maven3+`, `java17` 和 `lombok` 环境,修改 `YML` 配置信息,添加JVM参数`--add-opens=java.base/java.nio=org.apache.arrow.memory.core,ALL-UNNAMED`后启动 `manager` 服务即可。
|
||||
1. 此为前后端分离项目,本地代码调试需要分别启动后端工程 `hertzbeat-startup` 和前端工程 `web-app`
|
||||
2. 后端:需要 `maven3+`, `java17` 和 `lombok` 环境,修改 `YML` 配置信息,添加JVM参数`--add-opens=java.base/java.nio=org.apache.arrow.memory.core,ALL-UNNAMED`后启动 `hertzbeat-startup` 服务即可。
|
||||
3. 前端:需要 `nodejs npm angular-cli`环境,待本地后端启动后,在 `web-app` 目录下启动 `ng serve --open`
|
||||
4. 浏览器访问 `http://localhost:4200` 即可开始,默认账号密码 `admin/hertzbeat`
|
||||
|
||||
|
||||
+2
-2
@@ -155,8 +155,8 @@
|
||||
|
||||
##### 方式3:ローカルの実行
|
||||
|
||||
1. ローカルの実行には、バックエンドのプロジェクト`manager`とフロントエンドのプロジェクト`web-app`を起動する必要があります。
|
||||
2. バックエンド:`maven3+`、`java17`と`lombok`の環境は必要です。`YML` 設定を修正し、Java仮想マシンパラメータに`--add-opens=java.base/java.nio=org.apache.arrow.memory.core,ALL-UNNAMED` を追加し、`manager` を起動します。
|
||||
1. ローカルの実行には、バックエンドのプロジェクト`hertzbeat-startup`とフロントエンドのプロジェクト`web-app`を起動する必要があります。
|
||||
2. バックエンド:`maven3+`、`java17`と`lombok`の環境は必要です。`YML` 設定を修正し、Java仮想マシンパラメータに`--add-opens=java.base/java.nio=org.apache.arrow.memory.core,ALL-UNNAMED` を追加し、`hertzbeat-startup` を起動します。
|
||||
3. フロントエンド:`nodejs npm angular-cli`の環境は必要です。ローカルのバックエンドが立ち上がったら、`web-app` ディレクトリで `ng serve --open` というコマンドを実行します。
|
||||
4. スタート:`http://localhost:4200`にアクセスします。デフォルトのアカウントとパスワード:`admin/hertzbeat`。
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"intervals": 60,
|
||||
"tags": [],
|
||||
"app": "ftp",
|
||||
"host": "127.0.0.1",
|
||||
"instance": "127.0.0.1",
|
||||
"name": "{{.param.monitorFTP}}"
|
||||
},
|
||||
"params": [
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"intervals": 60,
|
||||
"tags": [],
|
||||
"app": "api",
|
||||
"host": "127.0.0.1",
|
||||
"instance": "127.0.0.1",
|
||||
"name": "{{.param.monitorHTTP}}"
|
||||
},
|
||||
"params": [
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"intervals": 60,
|
||||
"tags": [],
|
||||
"app": "ping",
|
||||
"host": "127.0.0.1",
|
||||
"instance": "127.0.0.1",
|
||||
"name": "{{.param.monitorPing}}"
|
||||
},
|
||||
"params": [
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"intervals": 60,
|
||||
"tags": [],
|
||||
"app": "port",
|
||||
"host": "127.0.0.1",
|
||||
"instance": "127.0.0.1",
|
||||
"name": "{{.param.monitorPort}}"
|
||||
},
|
||||
"params": [
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"intervals": 60,
|
||||
"tags": [],
|
||||
"app": "fullsite",
|
||||
"host": "127.0.0.1",
|
||||
"instance": "127.0.0.1",
|
||||
"name": "{{.param.monitorSiteMap}}"
|
||||
},
|
||||
"params": [
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"intervals": 60,
|
||||
"tags": [],
|
||||
"app": "ssl_cert",
|
||||
"host": "127.0.0.1",
|
||||
"instance": "127.0.0.1",
|
||||
"name": "{{.param.monitorSSL}}"
|
||||
},
|
||||
"params": [
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"intervals": 60,
|
||||
"tags": [],
|
||||
"app": "udp_port",
|
||||
"host": "127.0.0.1",
|
||||
"instance": "127.0.0.1",
|
||||
"name": "{{.param.monitorUDP}}"
|
||||
},
|
||||
"params": [
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"intervals": 60,
|
||||
"tags": [],
|
||||
"app": "website",
|
||||
"host": "127.0.0.1",
|
||||
"instance": "127.0.0.1",
|
||||
"name": "{{.param.monitorWebsite}}"
|
||||
},
|
||||
"params": [
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"intervals": 10,
|
||||
"tags": [],
|
||||
"app": "kubernetes",
|
||||
"host": "172.29.0.11",
|
||||
"instance": "172.29.0.11",
|
||||
"name": "Brave_Stingray_55yR"
|
||||
},
|
||||
"collector": "",
|
||||
|
||||
@@ -33,16 +33,17 @@ public interface MetricsTools {
|
||||
|
||||
/**
|
||||
* Get historical metrics data for a monitor
|
||||
* @param monitorId Monitor ID
|
||||
* @param app Monitor type (e.g., "linux", "mysql", "http")
|
||||
* @param metrics Metrics name (e.g., "system", "cpu", "memory")
|
||||
* @param metric Specific metric field (e.g., "usage", "used", "available")
|
||||
* @param label Label filter for specific instances
|
||||
* @param history Time range (e.g., "1h", "6h", "24h", "7d")
|
||||
*
|
||||
* @param instance Instance identifier (e.g., "ip:port", "ip", or "domain")
|
||||
* @param app Monitor type (e.g., "linux", "mysql", "http")
|
||||
* @param metrics Metrics name (e.g., "system", "cpu", "memory")
|
||||
* @param metric Specific metric field (e.g., "usage", "used", "available")
|
||||
* @param label Label filter for specific instances
|
||||
* @param history Time range (e.g., "1h", "6h", "24h", "7d")
|
||||
* @param interval Whether to aggregate data with intervals
|
||||
* @return Historical metrics data formatted for display
|
||||
*/
|
||||
String getHistoricalMetrics(Long monitorId, String app, String metrics, String metric, String label, String history, Boolean interval);
|
||||
String getHistoricalMetrics(String instance, String app, String metrics, String metric, String label, String history, Boolean interval);
|
||||
|
||||
/**
|
||||
* Check warehouse storage server status
|
||||
|
||||
@@ -152,7 +152,7 @@ public class MetricsToolsImpl implements MetricsTools {
|
||||
Ask user to provide the filters for labels, history and interval aggregation
|
||||
""")
|
||||
public String getHistoricalMetrics(
|
||||
@ToolParam(description = "Monitor ID", required = true) Long monitorId,
|
||||
@ToolParam(description = "Instance identifier (e.g., 'ip:port', 'ip', or 'domain')") String instance,
|
||||
@ToolParam(description = "Monitor type (e.g., 'linux', 'mysql', 'http')", required = true) String app,
|
||||
@ToolParam(description = "Metrics name (e.g., 'target', 'cpu', 'memory')", required = true) String metrics,
|
||||
@ToolParam(description = "Field Parameter (e.g., 'usage', 'used', 'available')", required = false) String fieldParameter,
|
||||
@@ -161,7 +161,7 @@ public class MetricsToolsImpl implements MetricsTools {
|
||||
@ToolParam(description = "Whether to aggregate data with intervals", required = false) Boolean interval) {
|
||||
|
||||
try {
|
||||
log.info("Getting historical metrics for monitor {} and metrics {}", monitorId, metrics);
|
||||
log.info("Getting historical metrics for monitor instance {} and metrics {}", instance, metrics);
|
||||
|
||||
if (history == null || history.trim().isEmpty()) {
|
||||
history = "24h";
|
||||
@@ -170,15 +170,15 @@ public class MetricsToolsImpl implements MetricsTools {
|
||||
interval = true;
|
||||
}
|
||||
|
||||
MetricsHistoryData historyData = metricsDataService.getMetricHistoryData(
|
||||
monitorId, app, metrics, fieldParameter, label, history, interval);
|
||||
MetricsHistoryData historyData = metricsDataService.getMetricHistoryData(instance,
|
||||
app, metrics, fieldParameter, history, interval);
|
||||
|
||||
if (historyData == null) {
|
||||
return String.format("No historical metrics data found for monitor ID %d and metrics '%s'", monitorId, metrics);
|
||||
return String.format("No historical metrics data found for monitor %s and metrics '%s'", instance, metrics);
|
||||
}
|
||||
|
||||
StringBuilder response = new StringBuilder();
|
||||
response.append("HISTORICAL METRICS: ").append(metrics).append(" (Monitor ID: ").append(monitorId).append(")\n");
|
||||
response.append("HISTORICAL METRICS: ").append(metrics).append(" (Monitor ID: ").append(instance).append(")\n");
|
||||
response.append("Time Range: ").append(history).append(" | Interval Aggregation: ").append(interval).append("\n");
|
||||
response.append("=".repeat(60)).append("\n\n");
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ import org.apache.hertzbeat.common.entity.manager.ParamDefine;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Implementation of Monitoring Tools functionality
|
||||
@@ -171,7 +172,7 @@ public class MonitorToolsImpl implements MonitorTools {
|
||||
response.append("ID: ").append(monitor.getId())
|
||||
.append(" | Name: ").append(monitor.getName())
|
||||
.append(" | Type: ").append(monitor.getApp())
|
||||
.append(" | Host: ").append(monitor.getHost())
|
||||
.append(" | Instance: ").append(monitor.getInstance())
|
||||
.append(" | Status: ").append(UtilityClass.getStatusText(monitor.getStatus()));
|
||||
|
||||
// Add creation date for better context
|
||||
@@ -257,12 +258,14 @@ public class MonitorToolsImpl implements MonitorTools {
|
||||
if (intervals == null || intervals < 10) {
|
||||
intervals = 600;
|
||||
}
|
||||
|
||||
|
||||
String instance = Objects.nonNull(port) ? host.trim() + ":" + port : host.trim();
|
||||
|
||||
// Create Monitor entity
|
||||
Monitor monitor = Monitor.builder()
|
||||
.name(name.trim())
|
||||
.app(app.toLowerCase().trim())
|
||||
.host(host.trim())
|
||||
.instance(instance)
|
||||
.intervals(intervals)
|
||||
.status((byte) 1)
|
||||
.type((byte) 0)
|
||||
|
||||
+24
-12
@@ -17,15 +17,6 @@
|
||||
|
||||
package org.apache.hertzbeat.alert.calculate.realtime;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.hertzbeat.alert.AlerterWorkerPool;
|
||||
@@ -41,11 +32,24 @@ import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
|
||||
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
|
||||
import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
import org.apache.hertzbeat.common.queue.CommonDataQueue;
|
||||
import org.apache.hertzbeat.common.support.exception.CommonDataQueueUnknownException;
|
||||
import org.apache.hertzbeat.common.util.BackoffUtils;
|
||||
import org.apache.hertzbeat.common.util.CommonUtil;
|
||||
import org.apache.hertzbeat.common.util.ExponentialBackoff;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Calculate alarms based on the alarm definition rules and collected data
|
||||
*/
|
||||
@@ -122,13 +126,22 @@ public class MetricsRealTimeAlertCalculator {
|
||||
*/
|
||||
public void startCalculate() {
|
||||
Runnable runnable = () -> {
|
||||
ExponentialBackoff backoff = new ExponentialBackoff(50L, 1000L);
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
try {
|
||||
CollectRep.MetricsData metricsData = dataQueue.pollMetricsDataToAlerter();
|
||||
if (metricsData == null) {
|
||||
continue;
|
||||
}
|
||||
backoff.reset();
|
||||
calculate(metricsData);
|
||||
dataQueue.sendMetricsDataToStorage(metricsData);
|
||||
} catch (InterruptedException ignored) {
|
||||
Thread.currentThread().interrupt();
|
||||
} catch (CommonDataQueueUnknownException ue) {
|
||||
if (!BackoffUtils.shouldContinueAfterBackoff(backoff)) {
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("calculate alarm error: {}.", e.getMessage(), e);
|
||||
}
|
||||
@@ -143,7 +156,7 @@ public class MetricsRealTimeAlertCalculator {
|
||||
long currentTimeMilli = System.currentTimeMillis();
|
||||
String instance = String.valueOf(metricsData.getId());
|
||||
String instanceName = metricsData.getInstanceName();
|
||||
String instanceHost = metricsData.getInstanceHost();
|
||||
String instanceHost = metricsData.getInstance();
|
||||
String app = metricsData.getApp();
|
||||
String metrics = metricsData.getMetrics();
|
||||
if ((CommonConstants.PROMETHEUS_APP_PREFIX + instanceName).equals(metricsData.getApp())) {
|
||||
@@ -192,12 +205,11 @@ public class MetricsRealTimeAlertCalculator {
|
||||
}
|
||||
Long defineId = define.getId();
|
||||
Map<String, String> commonFingerPrints = new HashMap<>(8);
|
||||
commonFingerPrints.put(CommonConstants.LABEL_INSTANCE, instance);
|
||||
// here use the alert name as finger, not care the alert name may be changed
|
||||
commonFingerPrints.put(CommonConstants.LABEL_DEFINE_ID, String.valueOf(define.getId()));
|
||||
commonFingerPrints.put(CommonConstants.LABEL_ALERT_NAME, define.getName());
|
||||
commonFingerPrints.put(CommonConstants.LABEL_INSTANCE_NAME, instanceName);
|
||||
commonFingerPrints.put(CommonConstants.LABEL_INSTANCE_HOST, instanceHost);
|
||||
commonFingerPrints.put(CommonConstants.LABEL_INSTANCE, instanceHost);
|
||||
commonFingerPrints.putAll(define.getLabels());
|
||||
if (labels != null) {
|
||||
commonFingerPrints.putAll(labels);
|
||||
|
||||
+14
-4
@@ -17,13 +17,17 @@
|
||||
|
||||
package org.apache.hertzbeat.alert.calculate.realtime;
|
||||
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.alert.calculate.realtime.window.LogWorker;
|
||||
import org.apache.hertzbeat.alert.calculate.realtime.window.TimeService;
|
||||
import org.apache.hertzbeat.common.entity.log.LogEntry;
|
||||
import org.apache.hertzbeat.common.queue.CommonDataQueue;
|
||||
import org.apache.hertzbeat.common.support.exception.CommonDataQueueUnknownException;
|
||||
import org.apache.hertzbeat.common.util.BackoffUtils;
|
||||
import org.apache.hertzbeat.common.util.ExponentialBackoff;
|
||||
import org.springframework.stereotype.Component;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
@@ -58,16 +62,22 @@ public class WindowedLogRealTimeAlertCalculator implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
ExponentialBackoff backoff = new ExponentialBackoff(50L, 1000L);
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
try {
|
||||
LogEntry logEntry = dataQueue.pollLogEntry();
|
||||
if (logEntry != null) {
|
||||
processLogEntry(logEntry);
|
||||
dataQueue.sendLogEntryToStorage(logEntry);
|
||||
if (logEntry == null) {
|
||||
continue;
|
||||
}
|
||||
backoff.reset();
|
||||
processLogEntry(logEntry);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
break;
|
||||
} catch (CommonDataQueueUnknownException ue) {
|
||||
if (!BackoffUtils.shouldContinueAfterBackoff(backoff)) {
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Error in log dispatch loop: {}", e.getMessage(), e);
|
||||
}
|
||||
|
||||
+487
@@ -0,0 +1,487 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.alert.notice.impl;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.hertzbeat.alert.AlerterProperties;
|
||||
import org.apache.hertzbeat.alert.notice.AlertNoticeException;
|
||||
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* FeiShu app alert notify impl
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class FeiShuAppAlertNotifyHandlerImpl extends AbstractAlertNotifyHandlerImpl {
|
||||
|
||||
/**
|
||||
* get tenant access_token url
|
||||
*/
|
||||
private static final String TENANT_ACCESS_TOKEN_URL = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal";
|
||||
|
||||
/**
|
||||
* get FeiShu app employee url
|
||||
*/
|
||||
private static final String EMPLOYEE_URL = "https://open.feishu.cn/open-apis/ehr/v1/employees?status=2&status=4&user_id_type=user_id&page_size=100";
|
||||
|
||||
/**
|
||||
* send FeiShu app message url
|
||||
*/
|
||||
private static final String APP_MESSAGE_URL = "https://open.feishu.cn/open-apis/im/v1/messages";
|
||||
|
||||
/**
|
||||
* send FeiShu app batch message url
|
||||
*/
|
||||
private static final String APP_BATCH_MESSAGE_URL = "https://open.feishu.cn/open-apis/message/v4/batch_send/";
|
||||
|
||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||
private static final byte USER_RECEIVE_TYPE = 0;
|
||||
private static final byte CHAT_RECEIVE_TYPE = 1;
|
||||
private static final byte PART_RECEIVE_TYPE = 2;
|
||||
private static final byte ALL_RECEIVE_TYPE = 3;
|
||||
|
||||
private final RestTemplate restTemplate;
|
||||
private final AlerterProperties alerterProperties;
|
||||
|
||||
@Override
|
||||
public void send(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) throws AlertNoticeException {
|
||||
var appId = receiver.getAppId();
|
||||
var appSecret = receiver.getAppSecret();
|
||||
var larkReceiveIdType = receiver.getLarkReceiveType();
|
||||
try {
|
||||
var accessToken = getAccessToken(appId, appSecret);
|
||||
var notificationContent = JsonUtil.toJson(renderContent(noticeTemplate, alert));
|
||||
JsonNode messageContent = createLarkMessage(receiver, notificationContent);
|
||||
switch (larkReceiveIdType) {
|
||||
case USER_RECEIVE_TYPE -> {
|
||||
String[] userIds = receiver.getUserId().split(",");
|
||||
if (userIds.length == 1) {
|
||||
sendLarkMessage(accessToken, "user_id", userIds[0], messageContent);
|
||||
} else {
|
||||
sendLarkUserBatchMessage(accessToken, userIds, messageContent);
|
||||
}
|
||||
}
|
||||
case CHAT_RECEIVE_TYPE -> sendLarkMessage(accessToken, "chat_id", receiver.getChatId(), messageContent);
|
||||
case PART_RECEIVE_TYPE ->
|
||||
sendLarkDepartmentBatchMessage(accessToken, receiver.getPartyId().split(","), messageContent);
|
||||
case ALL_RECEIVE_TYPE -> {
|
||||
List<String> userIds = new ArrayList<>();
|
||||
getLarkEmployeeUserIds(accessToken, null, userIds);
|
||||
sendLarkUserBatchMessage(accessToken, userIds.toArray(new String[0]), messageContent);
|
||||
}
|
||||
default -> throw new AlertNoticeException("Invalid larkReceiveIdType: " + larkReceiveIdType);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new AlertNoticeException("[FeiShu App Notify Error] " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte type() {
|
||||
return 14;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send FeiShu app message to chat or designated personnel
|
||||
*
|
||||
* @param accessToken Tenant access token
|
||||
* @param receiverIdType FeiShu app send message receiver id type: user_id, chat_id
|
||||
* @param receiverId FeiShu app user id or chat id
|
||||
* @param messageContent Message content
|
||||
* @see <a href="https://open.feishu.cn/document/server-docs/im-v1/batch_message/send-messages-in-batches">send message</a>
|
||||
*/
|
||||
private void sendLarkMessage(String accessToken, String receiverIdType, String receiverId, JsonNode messageContent) throws JsonProcessingException {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
headers.setBearerAuth(accessToken);
|
||||
FeiShuAppMessageDto messageDto = FeiShuAppMessageDto.builder()
|
||||
.receiveId(receiverId)
|
||||
.content(escapedCompactJson(messageContent))
|
||||
.build();
|
||||
HttpEntity<FeiShuAppMessageDto> request = new HttpEntity<>(messageDto, headers);
|
||||
call(APP_MESSAGE_URL + "?receive_id_type=" + receiverIdType, request, HttpMethod.POST, FeiShuAppResponse.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send FeiShu app department batch message
|
||||
*
|
||||
* @param accessToken Tenant access token
|
||||
* @param partyIds FeiShu app department ids
|
||||
* @param messageContent Message content
|
||||
* @see <a href="https://open.feishu.cn/document/server-docs/im-v1/batch_message/send-messages-in-batches">send batch message</a>
|
||||
*/
|
||||
private void sendLarkDepartmentBatchMessage(String accessToken, String[] partyIds, JsonNode messageContent) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
headers.setBearerAuth(accessToken);
|
||||
FeiShuAppBatchMessageDto batchMessageDto = FeiShuAppBatchMessageDto.builder()
|
||||
.departmentIds(partyIds)
|
||||
.card(messageContent)
|
||||
.build();
|
||||
HttpEntity<FeiShuAppBatchMessageDto> request = new HttpEntity<>(batchMessageDto, headers);
|
||||
call(APP_BATCH_MESSAGE_URL, request, HttpMethod.POST, FeiShuAppResponse.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send FeiShu app batch message to user
|
||||
*
|
||||
* @param accessToken Tenant access token
|
||||
* @param userIds FeiShu app user ids
|
||||
* @param messageContent Message content
|
||||
* @see <a href="https://open.feishu.cn/document/server-docs/im-v1/batch_message/send-messages-in-batches">send batch message</a>
|
||||
*/
|
||||
private void sendLarkUserBatchMessage(String accessToken, String[] userIds, JsonNode messageContent) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
headers.setBearerAuth(accessToken);
|
||||
FeiShuAppBatchMessageDto batchMessageDto = FeiShuAppBatchMessageDto.builder()
|
||||
.userIds(userIds)
|
||||
.card(messageContent)
|
||||
.build();
|
||||
HttpEntity<FeiShuAppBatchMessageDto> request = new HttpEntity<>(batchMessageDto, headers);
|
||||
FeiShuAppResponse call = call(APP_BATCH_MESSAGE_URL, request, HttpMethod.POST, FeiShuAppResponse.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get FeiShu app tenant access token
|
||||
*
|
||||
* @param appId Unique identifier for the application, obtained after creating the application
|
||||
* @param appSecret Application key, obtained after creating the application
|
||||
* @return Tenant access token
|
||||
* @see <a href="https://open.feishu.cn/document/server-docs/authentication-management/access-token/tenant_access_token_internal">tenant_access_token</a>
|
||||
*/
|
||||
private String getAccessToken(String appId, String appSecret) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
FeiShuAppAccessTokenDto accessTokenRequest = FeiShuAppAccessTokenDto.builder()
|
||||
.appId(appId)
|
||||
.appSecret(appSecret)
|
||||
.build();
|
||||
HttpEntity<FeiShuAppAccessTokenDto> request = new HttpEntity<>(accessTokenRequest, headers);
|
||||
FeiShuAppAccessTokenResponse data = call(TENANT_ACCESS_TOKEN_URL, request, HttpMethod.POST, FeiShuAppAccessTokenResponse.class);
|
||||
return data.getTenantAccessToken();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get FeiShu app employee user ids
|
||||
*
|
||||
* @param accessToken FeiShu app tenant access token
|
||||
* @param pageToken Paging marker, left blank for the first request, indicating traversal from scratch;
|
||||
* When there are more items in the pagination query result, a new page_token will be returned at the same time.
|
||||
* The next iteration can use this page_token to obtain the query result
|
||||
* @param userIds Collection for recursive padding
|
||||
* @see <a href="https://open.feishu.cn/document/server-docs/ehr-v1/list?appId=cli_a999532b1f52900b">https://open.feishu.cn/open-apis/ehr/v1/employees</a>
|
||||
*/
|
||||
private void getLarkEmployeeUserIds(String accessToken, String pageToken, List<String> userIds) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
headers.setBearerAuth(accessToken);
|
||||
HttpEntity<String> request = new HttpEntity<>(headers);
|
||||
var url = StringUtils.isNotBlank(pageToken) ? EMPLOYEE_URL + "&page_token=" + pageToken : EMPLOYEE_URL;
|
||||
FeiShuAppEmployeeResponse employeeResponse = call(url,
|
||||
request,
|
||||
HttpMethod.GET,
|
||||
FeiShuAppEmployeeResponse.class);
|
||||
if (Objects.equals(employeeResponse.getCode(), 0)) {
|
||||
userIds.addAll(employeeResponse.getData().getItems().stream().map(FeiShuAppEmployeeResponse.Employee::getUserId).toList());
|
||||
var hasMore = employeeResponse.getData().getHasMore();
|
||||
if (Boolean.TRUE.equals(hasMore)) {
|
||||
getLarkEmployeeUserIds(accessToken, employeeResponse.getData().getPageToken(), userIds);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private <R extends FeiShuAppResponse, E> R call(String url, HttpEntity<E> request, HttpMethod httpMethod, Class<R> responseType) {
|
||||
ResponseEntity<R> response = restTemplate.exchange(url, httpMethod, request, responseType);
|
||||
if (Objects.nonNull(response.getBody()) && !Objects.equals(response.getBody().getCode(), 0)) {
|
||||
log.warn("Send FeiShu App Error: {}", response.getBody().getMsg());
|
||||
throw new AlertNoticeException("Http StatusCode " + response.getStatusCode() + " Error: " + response.getBody().getMsg());
|
||||
}
|
||||
return response.getBody();
|
||||
}
|
||||
|
||||
private JsonNode createLarkMessage(NoticeReceiver receiver, String notificationContent) throws JsonProcessingException {
|
||||
String larkCardMessage = """
|
||||
{
|
||||
"schema": "2.0",
|
||||
"config": {
|
||||
"update_multi": true,
|
||||
"locales": [
|
||||
"en_us",
|
||||
"zh_cn"
|
||||
],
|
||||
"style": {
|
||||
"text_size": {
|
||||
"normal_v2": {
|
||||
"default": "normal",
|
||||
"pc": "normal",
|
||||
"mobile": "heading"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"direction": "vertical",
|
||||
"padding": "12px 12px 12px 12px",
|
||||
"elements": [
|
||||
{
|
||||
"tag": "markdown",
|
||||
"content": "%s",
|
||||
"i18n_content": {
|
||||
"en_us": ""
|
||||
},
|
||||
"text_align": "left",
|
||||
"text_size": "normal_v2",
|
||||
"margin": "0px 0px 0px 0px"
|
||||
},
|
||||
{
|
||||
"tag": "hr",
|
||||
"margin": "0px 0px 0px 0px"
|
||||
},
|
||||
{
|
||||
"tag": "column_set",
|
||||
"horizontal_align": "left",
|
||||
"columns": [
|
||||
{
|
||||
"tag": "column",
|
||||
"width": "weighted",
|
||||
"elements": [
|
||||
{
|
||||
"tag": "button",
|
||||
"text": {
|
||||
"tag": "plain_text",
|
||||
"content": "登入控制台",
|
||||
"i18n_content": {
|
||||
"en_us": "Login In"
|
||||
}
|
||||
},
|
||||
"type": "default",
|
||||
"width": "default",
|
||||
"size": "medium",
|
||||
"behaviors": [
|
||||
{
|
||||
"type": "open_url",
|
||||
"default_url": "%s",
|
||||
"pc_url": "",
|
||||
"ios_url": "",
|
||||
"android_url": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"direction": "horizontal",
|
||||
"vertical_spacing": "8px",
|
||||
"horizontal_align": "left",
|
||||
"vertical_align": "top",
|
||||
"weight": 1
|
||||
}
|
||||
],
|
||||
"margin": "0px 0px 0px 0px"
|
||||
}
|
||||
]
|
||||
},
|
||||
"header": {
|
||||
"title": {
|
||||
"tag": "plain_text",
|
||||
"content": "HertzBeat 告警",
|
||||
"i18n_content": {
|
||||
"en_us": "HertzBeat Alarm"
|
||||
}
|
||||
},
|
||||
"subtitle": {
|
||||
"tag": "plain_text",
|
||||
"content": ""
|
||||
},
|
||||
"template": "red",
|
||||
"padding": "12px 12px 12px 12px"
|
||||
}
|
||||
}
|
||||
""";
|
||||
Byte larkReceiveIdType = receiver.getLarkReceiveType();
|
||||
String userId = receiver.getUserId();
|
||||
String atUserElement = "";
|
||||
if (Objects.equals(larkReceiveIdType, CHAT_RECEIVE_TYPE) && StringUtils.isNotBlank(userId)) {
|
||||
atUserElement = "\\n" + Arrays.stream(userId.split(","))
|
||||
.map(id -> "<at id=" + id + "></at>")
|
||||
.collect(Collectors.joining(" "));
|
||||
}
|
||||
|
||||
if (notificationContent.startsWith("\"") && notificationContent.endsWith("\"")) {
|
||||
notificationContent = StringUtils.removeStart(notificationContent, "\"");
|
||||
notificationContent = StringUtils.removeEnd(notificationContent, "\"");
|
||||
}
|
||||
String jsonStr = String.format(larkCardMessage,
|
||||
notificationContent.replace("\"", "\\\"") + atUserElement,
|
||||
alerterProperties.getConsoleUrl());
|
||||
return OBJECT_MAPPER.readTree(jsonStr);
|
||||
}
|
||||
|
||||
|
||||
private String escapedCompactJson(JsonNode json) throws JsonProcessingException {
|
||||
return OBJECT_MAPPER.writeValueAsString(json);
|
||||
}
|
||||
|
||||
/**
|
||||
* feiShu app response
|
||||
*/
|
||||
@Data
|
||||
protected static class FeiShuAppResponse {
|
||||
|
||||
private Integer code;
|
||||
|
||||
private String msg;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* FeiShu app message get tenant access token request
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
protected static class FeiShuAppAccessTokenDto {
|
||||
|
||||
@JsonProperty("app_id")
|
||||
private String appId;
|
||||
|
||||
@JsonProperty("app_secret")
|
||||
private String appSecret;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* FeiShu app message get tenant access token response
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
protected static class FeiShuAppAccessTokenResponse extends FeiShuAppResponse {
|
||||
|
||||
@JsonProperty("tenant_access_token")
|
||||
private String tenantAccessToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* FeiShu app employee response
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
protected static class FeiShuAppEmployeeResponse extends FeiShuAppResponse {
|
||||
|
||||
private EmployeeResponseData data;
|
||||
|
||||
@Data
|
||||
private static class EmployeeResponseData {
|
||||
@JsonProperty("page_token")
|
||||
private String pageToken;
|
||||
|
||||
@JsonProperty("has_more")
|
||||
private Boolean hasMore;
|
||||
|
||||
private List<Employee> items;
|
||||
}
|
||||
|
||||
@Data
|
||||
private static class Employee {
|
||||
|
||||
@JsonProperty("user_id")
|
||||
private String userId;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* FeiShu app message request
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
protected static class FeiShuAppMessageDto {
|
||||
|
||||
@JsonProperty("receive_id")
|
||||
private String receiveId;
|
||||
|
||||
@Builder.Default
|
||||
@JsonProperty("msg_type")
|
||||
private String msgType = "interactive";
|
||||
|
||||
@JsonProperty("content")
|
||||
private String content;
|
||||
|
||||
@Builder.Default
|
||||
private final String uuid = UUID.randomUUID().toString();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* FeiShu app batch message request
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
protected static class FeiShuAppBatchMessageDto {
|
||||
|
||||
@Builder.Default
|
||||
@JsonProperty("msg_type")
|
||||
private final String msgType = "interactive";
|
||||
|
||||
private JsonNode card;
|
||||
|
||||
@JsonProperty("department_ids")
|
||||
private String[] departmentIds;
|
||||
|
||||
@JsonProperty("user_ids")
|
||||
private String[] userIds;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+5
@@ -48,6 +48,11 @@ final class WebHookAlertNotifyHandlerImpl extends AbstractAlertNotifyHandlerImpl
|
||||
}
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
if ("Basic".equalsIgnoreCase(receiver.getHookAuthType())) {
|
||||
headers.setBasicAuth(receiver.getHookAuthToken());
|
||||
} else if ("Bearer".equalsIgnoreCase(receiver.getHookAuthType())) {
|
||||
headers.setBearerAuth(receiver.getHookAuthToken());
|
||||
}
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
// alert.setContent(escapeJsonStr(alert.getContent()));
|
||||
String webhookJson = renderContent(noticeTemplate, alert);
|
||||
|
||||
+61
@@ -18,6 +18,7 @@
|
||||
package org.apache.hertzbeat.alert.service.impl;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.hertzbeat.alert.dto.HuaweiCloudExternAlert;
|
||||
@@ -43,6 +44,7 @@ import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.text.MessageFormat;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -70,6 +72,8 @@ public class HuaweiCloudExternAlertService implements ExternAlertService {
|
||||
|
||||
private static final String CHARSET_UTF8 = StandardCharsets.UTF_8.name();
|
||||
|
||||
private static final String SUBSCRIBE_URL_PREFIX = "https://console.huaweicloud.com/smn/subscription/confirm";
|
||||
|
||||
private final AlarmCommonReduce alarmCommonReduce;
|
||||
|
||||
public HuaweiCloudExternAlertService(AlarmCommonReduce alarmCommonReduce) {
|
||||
@@ -197,6 +201,9 @@ public class HuaweiCloudExternAlertService implements ExternAlertService {
|
||||
if (StringUtils.isBlank(subscribeUrl)) {
|
||||
return;
|
||||
}
|
||||
if (!subscribeUrl.startsWith(SUBSCRIBE_URL_PREFIX)) {
|
||||
throw new SecurityException("Untrusted domain: " + subscribeUrl);
|
||||
}
|
||||
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
|
||||
HttpGet httpGet = new HttpGet(subscribeUrl);
|
||||
try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
|
||||
@@ -303,6 +310,13 @@ public class HuaweiCloudExternAlertService implements ExternAlertService {
|
||||
*/
|
||||
private X509Certificate getCertificate(String signCertUrl) throws Exception {
|
||||
URL url = new URL(signCertUrl);
|
||||
if (!"https".equalsIgnoreCase(url.getProtocol())) {
|
||||
throw new SecurityException("Only HTTPS is allowed");
|
||||
}
|
||||
boolean trusted = Arrays.stream(Region.values()).anyMatch(ep -> ep.getEndpoint().equals(url.getHost()));
|
||||
if (!trusted) {
|
||||
throw new SecurityException("Untrusted domain: " + url.getHost());
|
||||
}
|
||||
try (InputStream in = url.openStream()) {
|
||||
CertificateFactory cf = CertificateFactory.getInstance(CERTIFICATE_TYPE);
|
||||
return (X509Certificate) cf.generateCertificate(in);
|
||||
@@ -340,4 +354,51 @@ public class HuaweiCloudExternAlertService implements ExternAlertService {
|
||||
return "huaweicloud-ces";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* doc: <a href="https://console.huaweicloud.com/apiexplorer/#/endpoint/SMN">SMN API</a>
|
||||
*/
|
||||
@Getter
|
||||
private enum Region {
|
||||
|
||||
AE_AD_1("ae-ad-1", "smn.ae-ad-1.myhuaweicloud.com"),
|
||||
AF_SOUTH_1("af-south-1", "smn.af-south-1.myhuaweicloud.com"),
|
||||
AP_SOUTHEAST_1("ap-southeast-1", "smn.ap-southeast-1.myhuaweicloud.com"),
|
||||
AP_SOUTHEAST_2("ap-southeast-2", "smn.ap-southeast-2.myhuaweicloud.com"),
|
||||
AP_SOUTHEAST_3("ap-southeast-3", "smn.ap-southeast-3.myhuaweicloud.com"),
|
||||
AP_SOUTHEAST_4("ap-southeast-4", "smn.ap-southeast-4.myhuaweicloud.com"),
|
||||
CN_EAST_2("cn-east-2", "smn.cn-east-2.myhuaweicloud.com"),
|
||||
CN_EAST_3("cn-east-3", "smn.cn-east-3.myhuaweicloud.com"),
|
||||
CN_EAST_4("cn-east-4", "smn.cn-east-4.myhuaweicloud.com"),
|
||||
CN_EAST_5("cn-east-5", "smn.cn-east-5.myhuaweicloud.com"),
|
||||
CN_NORTH_1("cn-north-1", "smn.cn-north-1.myhuaweicloud.com"),
|
||||
CN_NORTH_11("cn-north-11", "smn.cn-north-11.myhuaweicloud.com"),
|
||||
CN_NORTH_12("cn-north-12", "smn.cn-north-12.myhuaweicloud.com"),
|
||||
CN_NORTH_2("cn-north-2", "smn.cn-north-2.myhuaweicloud.cn"),
|
||||
CN_NORTH_4("cn-north-4", "smn.cn-north-4.myhuaweicloud.com"),
|
||||
CN_NORTH_9("cn-north-9", "smn.cn-north-9.myhuaweicloud.com"),
|
||||
CN_SOUTH_1("cn-south-1", "smn.cn-south-1.myhuaweicloud.com"),
|
||||
CN_SOUTH_2("cn-south-2", "smn.cn-south-2.myhuaweicloud.com"),
|
||||
CN_SOUTH_4("cn-south-4", "smn.cn-south-4.myhuaweicloud.com"),
|
||||
CN_SOUTHWEST_2("cn-southwest-2", "smn.cn-southwest-2.myhuaweicloud.com"),
|
||||
CN_SOUTHWEST_3("cn-southwest-3", "smn.cn-southwest-3.myhuaweicloud.com"),
|
||||
EU_WEST_0("eu-west-0", "smn.eu-west-0.myhuaweicloud.com"),
|
||||
LA_NORTH_2("la-north-2", "smn.la-north-2.myhuaweicloud.com"),
|
||||
LA_SOUTH_2("la-south-2", "smn.la-south-2.myhuaweicloud.com"),
|
||||
MY_KUALALUMPUR_1("my-kualalumpur-1", "smn.my-kualalumpur-1.myhuaweicloud.com"),
|
||||
NA_MEXICO_1("na-mexico-1", "smn.na-mexico-1.myhuaweicloud.com"),
|
||||
RU_MOSCOW_1("ru-moscow-1", "smn.ru-moscow-1.myhuaweicloud.com"),
|
||||
SA_BRAZIL_1("sa-brazil-1", "smn.sa-brazil-1.myhuaweicloud.com"),
|
||||
TR_WEST_1("tr-west-1", "smn.tr-west-1.myhuaweicloud.com"),
|
||||
EU_WEST_101("eu-west-101", "smn.eu-west-101.myhuaweicloud.eu");
|
||||
|
||||
private final String id;
|
||||
|
||||
private final String endpoint;
|
||||
|
||||
Region(String id, String endpoint) {
|
||||
this.id = id;
|
||||
this.endpoint = endpoint;
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -303,9 +303,8 @@ public class NoticeConfigServiceImpl implements NoticeConfigService, CommandLine
|
||||
@Override
|
||||
public boolean sendTestMsg(NoticeReceiver noticeReceiver) {
|
||||
Map<String, String> labels = new HashMap<>(8);
|
||||
labels.put(CommonConstants.LABEL_INSTANCE, "1000000");
|
||||
labels.put(CommonConstants.LABEL_INSTANCE, "127.0.0.1");
|
||||
labels.put(CommonConstants.LABEL_ALERT_NAME, "CPU Usage Alert");
|
||||
labels.put(CommonConstants.LABEL_INSTANCE_HOST, "127.0.0.1");
|
||||
Map<String, String> annotations = new HashMap<>(8);
|
||||
annotations.put("suggest", "Please check the CPU usage of the server");
|
||||
SingleAlert singleAlert1 = SingleAlert.builder()
|
||||
|
||||
@@ -88,7 +88,7 @@ public class CryptoUtils {
|
||||
* @return lowercase hexadecimal string
|
||||
*/
|
||||
public static String hmacSha256Hex(String key, String data) {
|
||||
return hmacSha256Hex(hmac256(key.getBytes(UTF8), data), data);
|
||||
return hmacSha256Hex(key.getBytes(UTF8), data);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+3
-3
@@ -125,7 +125,7 @@ public class MetricsRealTimeAlertCalculatorMatchTest {
|
||||
|
||||
Map<String, String> meta = new HashMap<>();
|
||||
meta.put(MetricDataConstants.INSTANCE_NAME, "Cool_Stingray_34Nj_copy");
|
||||
meta.put(MetricDataConstants.INSTANCE_HOST, "127.0.0.1");
|
||||
meta.put(MetricDataConstants.INSTANCE, "127.0.0.1");
|
||||
|
||||
builder.addMetadataAll(meta);
|
||||
builder.addAllFields(Lists.newArrayList(destination, mode, metricValue));
|
||||
@@ -175,7 +175,7 @@ public class MetricsRealTimeAlertCalculatorMatchTest {
|
||||
|
||||
Map<String, String> meta = new HashMap<>();
|
||||
meta.put(MetricDataConstants.INSTANCE_NAME, "Cool_Stingray_34Nj");
|
||||
meta.put(MetricDataConstants.INSTANCE_HOST, "127.0.0.1");
|
||||
meta.put(MetricDataConstants.INSTANCE, "127.0.0.1");
|
||||
|
||||
builder.addMetadataAll(meta);
|
||||
builder.addAllFields(Lists.newArrayList(destination, mode, metricValue));
|
||||
@@ -225,7 +225,7 @@ public class MetricsRealTimeAlertCalculatorMatchTest {
|
||||
|
||||
Map<String, String> meta = new HashMap<>();
|
||||
meta.put(MetricDataConstants.INSTANCE_NAME, "Vibrant_Gazelle_83vJ");
|
||||
meta.put(MetricDataConstants.INSTANCE_HOST, "127.0.0.1");
|
||||
meta.put(MetricDataConstants.INSTANCE, "127.0.0.1");
|
||||
|
||||
builder.addMetadataAll(meta);
|
||||
builder.addAllFields(Lists.newArrayList(responseTime));
|
||||
|
||||
-3
@@ -108,7 +108,6 @@ class WindowedLogRealTimeAlertCalculatorTest {
|
||||
verify(timeService).isLateData(anyLong());
|
||||
verify(timeService).updateMaxTimestamp(anyLong());
|
||||
verify(logWorker).reduceAndSendLogTask(validLogEntry);
|
||||
verify(dataQueue).sendLogEntryToStorage(validLogEntry);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -134,7 +133,6 @@ class WindowedLogRealTimeAlertCalculatorTest {
|
||||
verify(timeService, never()).isLateData(anyLong());
|
||||
verify(timeService, never()).updateMaxTimestamp(anyLong());
|
||||
verify(logWorker, never()).reduceAndSendLogTask(any());
|
||||
verify(dataQueue).sendLogEntryToStorage(invalidTimestampLogEntry);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -162,7 +160,6 @@ class WindowedLogRealTimeAlertCalculatorTest {
|
||||
verify(timeService).isLateData(anyLong());
|
||||
verify(timeService, never()).updateMaxTimestamp(anyLong());
|
||||
verify(logWorker, never()).reduceAndSendLogTask(any());
|
||||
verify(dataQueue).sendLogEntryToStorage(lateDataLogEntry);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+1
-1
@@ -88,7 +88,7 @@ class AlertDefineControllerTest {
|
||||
Monitor.builder()
|
||||
.id(1L)
|
||||
.app("app")
|
||||
.host("localhost")
|
||||
.instance("localhost")
|
||||
.name("monitor")
|
||||
.build()
|
||||
)
|
||||
|
||||
+333
@@ -0,0 +1,333 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.alert.notice.impl;
|
||||
|
||||
import org.apache.hertzbeat.alert.AlerterProperties;
|
||||
import org.apache.hertzbeat.alert.notice.AlertNoticeException;
|
||||
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
|
||||
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Test case for FeiShu App Alert Notify
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class FeiShuAppAlertNotifyHandlerImplTest {
|
||||
|
||||
@Mock
|
||||
private RestTemplate restTemplate;
|
||||
|
||||
@Mock
|
||||
private ResourceBundle bundle;
|
||||
|
||||
@Mock
|
||||
private AlerterProperties alerterProperties;
|
||||
|
||||
@InjectMocks
|
||||
private FeiShuAppAlertNotifyHandlerImpl feiShuAppAlertNotifyHandler;
|
||||
|
||||
private NoticeReceiver receiver;
|
||||
private GroupAlert groupAlert;
|
||||
private NoticeTemplate template;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
receiver = new NoticeReceiver();
|
||||
receiver.setId(1L);
|
||||
receiver.setName("test-receiver");
|
||||
receiver.setType((byte) 14);
|
||||
receiver.setAppId("cli-test-app-id");
|
||||
receiver.setAppSecret("test-app-secret");
|
||||
|
||||
groupAlert = new GroupAlert();
|
||||
SingleAlert singleAlert = new SingleAlert();
|
||||
singleAlert.setLabels(new HashMap<>());
|
||||
singleAlert.getLabels().put("severity", "critical");
|
||||
singleAlert.getLabels().put("alertname", "Test Alert");
|
||||
|
||||
List<SingleAlert> alerts = new ArrayList<>();
|
||||
alerts.add(singleAlert);
|
||||
groupAlert.setAlerts(alerts);
|
||||
|
||||
template = new NoticeTemplate();
|
||||
template.setId(1L);
|
||||
template.setName("test-template");
|
||||
template.setContent("test content");
|
||||
|
||||
lenient().when(bundle.getString("alerter.notify.title")).thenReturn("Alert Notification");
|
||||
lenient().when(alerterProperties.getConsoleUrl()).thenReturn("https://console.hertzbeat.com");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test successful notification to single user
|
||||
*/
|
||||
@Test
|
||||
public void testNotifyAlertSuccessSingleUser() {
|
||||
// Setup receiver for single user
|
||||
receiver.setLarkReceiveType((byte) 0);
|
||||
receiver.setUserId("user-001");
|
||||
|
||||
// Mock access token response
|
||||
FeiShuAppAlertNotifyHandlerImpl.FeiShuAppAccessTokenResponse accessTokenResp =
|
||||
new FeiShuAppAlertNotifyHandlerImpl.FeiShuAppAccessTokenResponse();
|
||||
accessTokenResp.setCode(0);
|
||||
accessTokenResp.setMsg("success");
|
||||
accessTokenResp.setTenantAccessToken("test-access-token");
|
||||
|
||||
// Mock message send response
|
||||
FeiShuAppAlertNotifyHandlerImpl.FeiShuAppResponse messageResp =
|
||||
new FeiShuAppAlertNotifyHandlerImpl.FeiShuAppResponse();
|
||||
messageResp.setCode(0);
|
||||
messageResp.setMsg("success");
|
||||
|
||||
// Mock restTemplate calls
|
||||
when(restTemplate.exchange(
|
||||
anyString(),
|
||||
eq(org.springframework.http.HttpMethod.POST),
|
||||
any(),
|
||||
eq(FeiShuAppAlertNotifyHandlerImpl.FeiShuAppAccessTokenResponse.class)))
|
||||
.thenReturn(new ResponseEntity<>(accessTokenResp, HttpStatus.OK));
|
||||
|
||||
when(restTemplate.exchange(
|
||||
anyString(),
|
||||
eq(org.springframework.http.HttpMethod.POST),
|
||||
any(),
|
||||
eq(FeiShuAppAlertNotifyHandlerImpl.FeiShuAppResponse.class)))
|
||||
.thenReturn(new ResponseEntity<>(messageResp, HttpStatus.OK));
|
||||
feiShuAppAlertNotifyHandler.send(receiver, template, groupAlert);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test successful notification to multiple users
|
||||
*/
|
||||
@Test
|
||||
public void testNotifyAlertSuccessMultipleUsers() {
|
||||
receiver.setLarkReceiveType((byte) 0);
|
||||
receiver.setUserId("user-001,user-002,user-003");
|
||||
|
||||
FeiShuAppAlertNotifyHandlerImpl.FeiShuAppAccessTokenResponse accessTokenResp =
|
||||
new FeiShuAppAlertNotifyHandlerImpl.FeiShuAppAccessTokenResponse();
|
||||
accessTokenResp.setCode(0);
|
||||
accessTokenResp.setMsg("success");
|
||||
accessTokenResp.setTenantAccessToken("test-access-token");
|
||||
|
||||
FeiShuAppAlertNotifyHandlerImpl.FeiShuAppResponse messageResp =
|
||||
new FeiShuAppAlertNotifyHandlerImpl.FeiShuAppResponse();
|
||||
messageResp.setCode(0);
|
||||
messageResp.setMsg("success");
|
||||
|
||||
when(restTemplate.exchange(
|
||||
anyString(),
|
||||
eq(org.springframework.http.HttpMethod.POST),
|
||||
any(),
|
||||
eq(FeiShuAppAlertNotifyHandlerImpl.FeiShuAppAccessTokenResponse.class)))
|
||||
.thenReturn(new ResponseEntity<>(accessTokenResp, HttpStatus.OK));
|
||||
|
||||
when(restTemplate.exchange(
|
||||
anyString(),
|
||||
eq(org.springframework.http.HttpMethod.POST),
|
||||
any(),
|
||||
eq(FeiShuAppAlertNotifyHandlerImpl.FeiShuAppResponse.class)))
|
||||
.thenReturn(new ResponseEntity<>(messageResp, HttpStatus.OK));
|
||||
|
||||
feiShuAppAlertNotifyHandler.send(receiver, template, groupAlert);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test successful notification to chat
|
||||
*/
|
||||
@Test
|
||||
public void testNotifyAlertSuccessChat() {
|
||||
receiver.setLarkReceiveType((byte) 1);
|
||||
receiver.setChatId("chat-001");
|
||||
|
||||
FeiShuAppAlertNotifyHandlerImpl.FeiShuAppAccessTokenResponse accessTokenResp =
|
||||
new FeiShuAppAlertNotifyHandlerImpl.FeiShuAppAccessTokenResponse();
|
||||
accessTokenResp.setCode(0);
|
||||
accessTokenResp.setMsg("success");
|
||||
accessTokenResp.setTenantAccessToken("test-access-token");
|
||||
|
||||
FeiShuAppAlertNotifyHandlerImpl.FeiShuAppResponse messageResp =
|
||||
new FeiShuAppAlertNotifyHandlerImpl.FeiShuAppResponse();
|
||||
messageResp.setCode(0);
|
||||
messageResp.setMsg("success");
|
||||
|
||||
when(restTemplate.exchange(
|
||||
anyString(),
|
||||
eq(org.springframework.http.HttpMethod.POST),
|
||||
any(),
|
||||
eq(FeiShuAppAlertNotifyHandlerImpl.FeiShuAppAccessTokenResponse.class)))
|
||||
.thenReturn(new ResponseEntity<>(accessTokenResp, HttpStatus.OK));
|
||||
|
||||
when(restTemplate.exchange(
|
||||
anyString(),
|
||||
eq(org.springframework.http.HttpMethod.POST),
|
||||
any(),
|
||||
eq(FeiShuAppAlertNotifyHandlerImpl.FeiShuAppResponse.class)))
|
||||
.thenReturn(new ResponseEntity<>(messageResp, HttpStatus.OK));
|
||||
|
||||
feiShuAppAlertNotifyHandler.send(receiver, template, groupAlert);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test successful notification to departments
|
||||
*/
|
||||
@Test
|
||||
public void testNotifyAlertSuccessDepartments() {
|
||||
receiver.setLarkReceiveType((byte) 2);
|
||||
receiver.setPartyId("dept-001,dept-002");
|
||||
|
||||
FeiShuAppAlertNotifyHandlerImpl.FeiShuAppAccessTokenResponse accessTokenResp =
|
||||
new FeiShuAppAlertNotifyHandlerImpl.FeiShuAppAccessTokenResponse();
|
||||
accessTokenResp.setCode(0);
|
||||
accessTokenResp.setMsg("success");
|
||||
accessTokenResp.setTenantAccessToken("test-access-token");
|
||||
|
||||
FeiShuAppAlertNotifyHandlerImpl.FeiShuAppResponse messageResp =
|
||||
new FeiShuAppAlertNotifyHandlerImpl.FeiShuAppResponse();
|
||||
messageResp.setCode(0);
|
||||
messageResp.setMsg("success");
|
||||
|
||||
when(restTemplate.exchange(
|
||||
anyString(),
|
||||
eq(org.springframework.http.HttpMethod.POST),
|
||||
any(),
|
||||
eq(FeiShuAppAlertNotifyHandlerImpl.FeiShuAppAccessTokenResponse.class)))
|
||||
.thenReturn(new ResponseEntity<>(accessTokenResp, HttpStatus.OK));
|
||||
|
||||
when(restTemplate.exchange(
|
||||
anyString(),
|
||||
eq(org.springframework.http.HttpMethod.POST),
|
||||
any(),
|
||||
eq(FeiShuAppAlertNotifyHandlerImpl.FeiShuAppResponse.class)))
|
||||
.thenReturn(new ResponseEntity<>(messageResp, HttpStatus.OK));
|
||||
|
||||
feiShuAppAlertNotifyHandler.send(receiver, template, groupAlert);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test notification failure due to access token error
|
||||
*/
|
||||
@Test
|
||||
public void testNotifyAlertFailureAccessToken() {
|
||||
receiver.setLarkReceiveType((byte) 0);
|
||||
receiver.setUserId("user-001");
|
||||
|
||||
FeiShuAppAlertNotifyHandlerImpl.FeiShuAppAccessTokenResponse accessTokenResp =
|
||||
new FeiShuAppAlertNotifyHandlerImpl.FeiShuAppAccessTokenResponse();
|
||||
accessTokenResp.setCode(999);
|
||||
accessTokenResp.setMsg("Invalid app credentials");
|
||||
|
||||
when(restTemplate.exchange(
|
||||
anyString(),
|
||||
eq(org.springframework.http.HttpMethod.POST),
|
||||
any(),
|
||||
eq(FeiShuAppAlertNotifyHandlerImpl.FeiShuAppAccessTokenResponse.class)))
|
||||
.thenReturn(new ResponseEntity<>(accessTokenResp, HttpStatus.OK));
|
||||
|
||||
assertThrows(AlertNoticeException.class, () -> {
|
||||
feiShuAppAlertNotifyHandler.send(receiver, template, groupAlert);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test notification failure due to message send error
|
||||
*/
|
||||
@Test
|
||||
public void testNotifyAlertFailureMessageSend() {
|
||||
receiver.setLarkReceiveType((byte) 0);
|
||||
receiver.setUserId("user-001");
|
||||
|
||||
FeiShuAppAlertNotifyHandlerImpl.FeiShuAppAccessTokenResponse accessTokenResp =
|
||||
new FeiShuAppAlertNotifyHandlerImpl.FeiShuAppAccessTokenResponse();
|
||||
accessTokenResp.setCode(0);
|
||||
accessTokenResp.setMsg("success");
|
||||
accessTokenResp.setTenantAccessToken("test-access-token");
|
||||
|
||||
FeiShuAppAlertNotifyHandlerImpl.FeiShuAppResponse messageResp =
|
||||
new FeiShuAppAlertNotifyHandlerImpl.FeiShuAppResponse();
|
||||
messageResp.setCode(999);
|
||||
messageResp.setMsg("User not found");
|
||||
|
||||
when(restTemplate.exchange(
|
||||
anyString(),
|
||||
eq(org.springframework.http.HttpMethod.POST),
|
||||
any(),
|
||||
eq(FeiShuAppAlertNotifyHandlerImpl.FeiShuAppAccessTokenResponse.class)))
|
||||
.thenReturn(new ResponseEntity<>(accessTokenResp, HttpStatus.OK));
|
||||
|
||||
when(restTemplate.exchange(
|
||||
anyString(),
|
||||
eq(org.springframework.http.HttpMethod.POST),
|
||||
any(),
|
||||
eq(FeiShuAppAlertNotifyHandlerImpl.FeiShuAppResponse.class)))
|
||||
.thenReturn(new ResponseEntity<>(messageResp, HttpStatus.OK));
|
||||
|
||||
assertThrows(AlertNoticeException.class, () -> {
|
||||
feiShuAppAlertNotifyHandler.send(receiver, template, groupAlert);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test invalid larkReceiveType
|
||||
*/
|
||||
@Test
|
||||
public void testInvalidLarkReceiveType() {
|
||||
receiver.setLarkReceiveType((byte) 99);
|
||||
receiver.setUserId("user-001");
|
||||
|
||||
FeiShuAppAlertNotifyHandlerImpl.FeiShuAppAccessTokenResponse accessTokenResp =
|
||||
new FeiShuAppAlertNotifyHandlerImpl.FeiShuAppAccessTokenResponse();
|
||||
accessTokenResp.setCode(0);
|
||||
accessTokenResp.setMsg("success");
|
||||
accessTokenResp.setTenantAccessToken("test-access-token");
|
||||
|
||||
when(restTemplate.exchange(
|
||||
anyString(),
|
||||
eq(org.springframework.http.HttpMethod.POST),
|
||||
any(),
|
||||
eq(FeiShuAppAlertNotifyHandlerImpl.FeiShuAppAccessTokenResponse.class)))
|
||||
.thenReturn(new ResponseEntity<>(accessTokenResp, HttpStatus.OK));
|
||||
|
||||
assertThrows(AlertNoticeException.class, () -> {
|
||||
feiShuAppAlertNotifyHandler.send(receiver, template, groupAlert);
|
||||
});
|
||||
}
|
||||
}
|
||||
+36
-1
@@ -146,7 +146,42 @@ public class HuaweiCloudExternAlertServiceTest {
|
||||
verify(alarmCommonReduce, never()).reduceAndSendAlarm(any(SingleAlert.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSubscriptionUrlFail() {
|
||||
HuaweiCloudExternAlert externAlert = new HuaweiCloudExternAlert();
|
||||
externAlert.setSubscribeUrl("https://console.huaweicloud.com/smn/subscription/confirm?token=477a784601d744e4ab9ab83986502d31c4b938"
|
||||
+ "0ec0b64392b134e517c3aa17eb7b3a12dc9f3b4ab495e61c4dee654b435d7223ea934345bf8ae8901cef912b1d&topic_urn=urn:smn:cn-north-4"
|
||||
+ ":477a784601d744e4ab9ab83986502d31:CES_notification_group_bngJ2aMpX®ion=cn-north-4");
|
||||
externAlert.setSignature("ottf37C/2RdDgqimRQMIBU6i7XjUfPPMU760jJn71wwP3825YPoIT22uw2A9399rkm9Jrt1qUEFrDLuA5yHFLd5n/XoM4FghIgyFn7VIfgpuVM31a+co78s"
|
||||
+ "YBiZ1egOCE/AwFm2oygRhfIceUj9Kw9vmc06el9TXY6RtE5tAEF6qEmICtTh45KwtCO/WRs3DY72dQi5hm0w7/tktS4WFZ1iP4LHt5eCwFvnH0u29Y96cJNI0fLUQxI5MkhgjK"
|
||||
+ "77JkFK7UT6ZYJZhzgSp/B7OQGStOQx+3Duvx4T4CzccZQM3sca81Z0B0GFGWeVXuEHyCPLsayY/Iz+5Tco51elT8w==");
|
||||
externAlert.setTopicUrn("urn:smn:cn-north-4:477a784601d744e4ab9ab83986502d31:CES_notification_group_bngJ2aMpX");
|
||||
externAlert.setMessageId("242fac183d3a4936b5ead6c725a32ed0");
|
||||
externAlert.setType("SubscriptionConfirmation");
|
||||
externAlert.setMessage("You are invited to subscribe to topic: urn:smn:cn-north-4:477a784601d744e4ab9ab83986502d31:"
|
||||
+ "CES_notification_group_bngJ2aMpX. To confirm this subscription, please visit the subscribe_url included in this message. The subscribe_url is valid only within 48 hours.");
|
||||
externAlert.setSigningCertUrl("https://xxxx.myhuaweicloud.com/smn/SMN_cn-north-4_b98100ca131b4116ab8ee7ccedbaae99.pem");
|
||||
externAlert.setTimestamp("2025-06-07T15:07:14Z");
|
||||
externAlertService.addExternAlert(JsonUtil.toJson(externAlert));
|
||||
verify(alarmCommonReduce, never()).reduceAndSendAlarm(any(SingleAlert.class));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testCertUrlFailed() {
|
||||
HuaweiCloudExternAlert externAlert = new HuaweiCloudExternAlert();
|
||||
externAlert.setSignature("TImrLoeb0tV1JZJSPyA0rpC9mNqH3MmhwQ4tgpuHHa+JztfGVZFvkU//OthKKhzpDAoYiXOYG9DbzXCLb"
|
||||
+ "vaGePIRITakoynYyYr9zZIpdx9jXhQNlgF8np1+t0JxNeoIq0DYWgH52tsodwqOm+OnmkcHwCRo/1rFv85KrKAaX2gy3sNwX"
|
||||
+ "w1hKnAwAw0mJlxHHSf/N3+7j6GoxCNV7fN9K4CpJiLMGNvUa7zVmG0U9mPvt/7Lac155kPPQ9lYyeL7vVI0e4sfRbuQruz3E"
|
||||
+ "0ZP40TKx0afoeR0/Bx/IoZzRP1La7pKlbEISvkcM7TqW/IOGQTkhVsQ32RFRxZWO2snw==");
|
||||
externAlert.setSubject("DCS Redis “dcs-h4tv” ");
|
||||
externAlert.setTopicUrn("urn:smn:cn-north-4:477a784601d744e4ab9ab83986502d31:CES_notification_group_bngJ2aMpX");
|
||||
externAlert.setMessageId("d3672d737bb742cf8c2aa3f0fd72d4d1");
|
||||
externAlert.setType("Notification");
|
||||
externAlert.setMessage("{}");
|
||||
externAlert.setSigningCertUrl("https://xxx.myhuaweicloud.com/failedUrl");
|
||||
externAlert.setTimestamp("2025-06-07T15:12:09Z");
|
||||
externAlertService.addExternAlert(JsonUtil.toJson(externAlert));
|
||||
verify(alarmCommonReduce, never()).reduceAndSendAlarm(any(SingleAlert.class));
|
||||
}
|
||||
|
||||
}
|
||||
+1
-1
@@ -42,7 +42,7 @@ public class CryptoUtilsTest {
|
||||
@Test
|
||||
void testHmacSha256Hex() {
|
||||
String signature = CryptoUtils.hmacSha256Hex("your-real-key", "your-real-data");;
|
||||
assertEquals("41878ccd7ecd795a2dd7ec39be7f33fed4be3ec75f5307689e39dd6f41fdbaac", signature);
|
||||
assertEquals("f09adf5f4bf94eddecf0f7c8f39a3a8dc7f95ccdc2faf2e5330bc5a78e4bba95", signature);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -109,6 +109,12 @@
|
||||
<artifactId>orai18n</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<!-- db2 -->
|
||||
<dependency>
|
||||
<groupId>com.ibm.db2</groupId>
|
||||
<artifactId>jcc</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<!--redis-->
|
||||
<dependency>
|
||||
<groupId>io.lettuce</groupId>
|
||||
@@ -152,7 +158,6 @@
|
||||
</dependency>
|
||||
|
||||
|
||||
|
||||
<!--plc-->
|
||||
<dependency>
|
||||
<groupId>org.apache.plc4x</groupId>
|
||||
|
||||
+10
@@ -172,6 +172,14 @@ public class JdbcCommonCollect extends AbstractCollect {
|
||||
".*en\\s*([/\\\\]|\\\\n|/n|\\n)\\s*crypt.*",
|
||||
".*ci\\s*([/\\\\]|\\\\n|/n|\\n)\\s*pher.*"
|
||||
});
|
||||
|
||||
// DB2 bypass mode
|
||||
PLATFORM_BYPASS_PATTERNS.put("db2", new String[]{
|
||||
".*security\\s*([/\\\\]|\\\\n|/n|\\n)\\s*mechanism.*",
|
||||
".*client\\s*([/\\\\]|\\\\n|/n|\\n)\\s*accounting.*",
|
||||
".*ssl\\s*([/\\\\]|\\\\n|/n|\\n)\\s*connection.*",
|
||||
".*use\\s*([/\\\\]|\\\\n|/n|\\n)\\s*client\\s*([/\\\\]|\\\\n|/n|\\n)\\s*information.*"
|
||||
});
|
||||
}
|
||||
|
||||
private final GlobalConnectionCache connectionCommonCache = GlobalConnectionCache.getInstance();
|
||||
@@ -531,6 +539,8 @@ public class JdbcCommonCollect extends AbstractCollect {
|
||||
case "oracle" -> "jdbc:oracle:thin:@" + host + ":" + port
|
||||
+ "/" + (jdbcProtocol.getDatabase() == null ? "" : jdbcProtocol.getDatabase());
|
||||
case "dm" -> "jdbc:dm://" + host + ":" + port;
|
||||
case "db2" -> "jdbc:db2://" + host + ":" + port
|
||||
+ "/" + (jdbcProtocol.getDatabase() == null ? "" : jdbcProtocol.getDatabase());
|
||||
case "testcontainers" -> "jdbc:tc:" + host + ":" + port
|
||||
+ ":///" + (jdbcProtocol.getDatabase() == null ? "" : jdbcProtocol.getDatabase()) + "?user=root&password=root";
|
||||
default -> throw new IllegalArgumentException("Not support database platform: " + jdbcProtocol.getPlatform());
|
||||
|
||||
+15
-6
@@ -67,6 +67,8 @@ public class RedisCommonCollectImpl extends AbstractCollect {
|
||||
|
||||
private static final String CLUSTER = "3";
|
||||
|
||||
private static final String SINGLE = "1";
|
||||
|
||||
private static final String CLUSTER_INFO = "cluster";
|
||||
|
||||
private static final String UNIQUE_IDENTITY = "identity";
|
||||
@@ -140,7 +142,7 @@ public class RedisCommonCollectImpl extends AbstractCollect {
|
||||
* @return data
|
||||
*/
|
||||
private List<Map<String, String>> getClusterRedisInfo(Metrics metrics) throws GeneralSecurityException, IOException {
|
||||
Map<String, StatefulRedisClusterConnection<String, String>> connectionMap = getConnectionList(metrics.getRedis());
|
||||
Map<String, StatefulRedisConnection<String, String>> connectionMap = getConnectionList(metrics.getRedis());
|
||||
List<Map<String, String>> list = new ArrayList<>(connectionMap.size());
|
||||
connectionMap.forEach((identity, connection) ->{
|
||||
String info = connection.sync().info(metrics.getName());
|
||||
@@ -214,16 +216,23 @@ public class RedisCommonCollectImpl extends AbstractCollect {
|
||||
* @param redisProtocol protocol
|
||||
* @return connection map
|
||||
*/
|
||||
private Map<String, StatefulRedisClusterConnection<String, String>> getConnectionList(RedisProtocol redisProtocol) throws GeneralSecurityException, IOException {
|
||||
private Map<String, StatefulRedisConnection<String, String>> getConnectionList(RedisProtocol redisProtocol) throws GeneralSecurityException, IOException {
|
||||
// first connection
|
||||
StatefulRedisClusterConnection<String, String> connection = getClusterConnection(redisProtocol);
|
||||
Partitions partitions = connection.getPartitions();
|
||||
Map<String, StatefulRedisClusterConnection<String, String>> clusterConnectionMap = new HashMap<>(partitions.size());
|
||||
Map<String, StatefulRedisConnection<String, String>> clusterConnectionMap = new HashMap<>(partitions.size());
|
||||
for (RedisClusterNode partition : partitions) {
|
||||
RedisURI uri = partition.getUri();
|
||||
redisProtocol.setHost(uri.getHost());
|
||||
redisProtocol.setPort(String.valueOf(uri.getPort()));
|
||||
StatefulRedisClusterConnection<String, String> clusterConnection = getClusterConnection(redisProtocol);
|
||||
RedisProtocol singleRedisProtocol = RedisProtocol.builder()
|
||||
.host(uri.getHost())
|
||||
.port(String.valueOf(uri.getPort()))
|
||||
.username(redisProtocol.getUsername())
|
||||
.password(redisProtocol.getPassword())
|
||||
.pattern(SINGLE)
|
||||
.timeout(redisProtocol.getTimeout())
|
||||
.sshTunnel(redisProtocol.getSshTunnel())
|
||||
.build();
|
||||
StatefulRedisConnection<String, String> clusterConnection = getSingleConnection(singleRedisProtocol);
|
||||
clusterConnectionMap.put(doUri(uri.getHost(), uri.getPort()), clusterConnection);
|
||||
}
|
||||
return clusterConnectionMap;
|
||||
|
||||
+30
-12
@@ -19,10 +19,13 @@ package org.apache.hertzbeat.collector.collect.redis;
|
||||
|
||||
import static org.apache.hertzbeat.common.constants.CommonConstants.TYPE_STRING;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import io.lettuce.core.RedisClient;
|
||||
import io.lettuce.core.RedisURI;
|
||||
import io.lettuce.core.api.StatefulRedisConnection;
|
||||
import io.lettuce.core.api.sync.RedisCommands;
|
||||
import io.lettuce.core.cluster.RedisClusterClient;
|
||||
import io.lettuce.core.cluster.api.StatefulRedisClusterConnection;
|
||||
import io.lettuce.core.cluster.api.sync.RedisAdvancedClusterCommands;
|
||||
import io.lettuce.core.cluster.models.partitions.Partitions;
|
||||
import io.lettuce.core.cluster.models.partitions.RedisClusterNode;
|
||||
import io.lettuce.core.resource.ClientResources;
|
||||
@@ -37,6 +40,7 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
@@ -51,13 +55,19 @@ public class RedisClusterCollectImplTest {
|
||||
|
||||
|
||||
@Mock
|
||||
private StatefulRedisClusterConnection<String, String> connection;
|
||||
private StatefulRedisClusterConnection<String, String> clusterConnection;
|
||||
|
||||
@Mock
|
||||
private RedisAdvancedClusterCommands<String, String> cmd;
|
||||
private StatefulRedisConnection<String, String> singleConnection;
|
||||
|
||||
@Mock
|
||||
private RedisClusterClient client;
|
||||
private RedisCommands<String, String> cmd;
|
||||
|
||||
@Mock
|
||||
private RedisClusterClient clusterClient;
|
||||
|
||||
@Mock
|
||||
private RedisClient singleClient;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
@@ -65,8 +75,10 @@ public class RedisClusterCollectImplTest {
|
||||
|
||||
@AfterEach
|
||||
void setDown() {
|
||||
connection.close();
|
||||
client.shutdown();
|
||||
clusterConnection.close();
|
||||
singleConnection.close();
|
||||
clusterClient.shutdown();
|
||||
singleClient.shutdown();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -110,10 +122,14 @@ public class RedisClusterCollectImplTest {
|
||||
metrics.setAliasFields(aliasField);
|
||||
metrics.setFields(fields);
|
||||
|
||||
|
||||
Mockito.mockStatic(RedisClusterClient.class).when(() -> RedisClusterClient.create(Mockito.any(ClientResources.class),
|
||||
Mockito.any(RedisURI.class))).thenReturn(client);
|
||||
Mockito.when(client.connect()).thenReturn(connection);
|
||||
MockedStatic<RedisClusterClient> redisClusterClientMockedStatic = Mockito.mockStatic(RedisClusterClient.class);
|
||||
redisClusterClientMockedStatic.when(() -> RedisClusterClient.create(Mockito.any(ClientResources.class),
|
||||
Mockito.any(RedisURI.class))).thenReturn(clusterClient);
|
||||
Mockito.when(clusterClient.connect()).thenReturn(clusterConnection);
|
||||
MockedStatic<RedisClient> redisClientMockedStatic = Mockito.mockStatic(RedisClient.class);
|
||||
redisClientMockedStatic.when(() -> RedisClient.create(Mockito.any(ClientResources.class),
|
||||
Mockito.any(RedisURI.class))).thenReturn(singleClient);
|
||||
Mockito.when(singleClient.connect()).thenReturn(singleConnection);
|
||||
|
||||
Partitions partitions = new Partitions();
|
||||
RedisClusterNode node = new RedisClusterNode();
|
||||
@@ -125,9 +141,9 @@ public class RedisClusterCollectImplTest {
|
||||
node2.setUri(RedisURI.create("redis://" + uri2));
|
||||
partitions.add(node2);
|
||||
|
||||
Mockito.when(connection.getPartitions()).thenReturn(partitions);
|
||||
Mockito.when(clusterConnection.getPartitions()).thenReturn(partitions);
|
||||
|
||||
Mockito.when(connection.sync()).thenReturn(cmd);
|
||||
Mockito.when(singleConnection.sync()).thenReturn(cmd);
|
||||
Mockito.when(cmd.info(metrics.getName())).thenReturn(info);
|
||||
Mockito.when(cmd.clusterInfo()).thenReturn(clusterInfo);
|
||||
|
||||
@@ -147,6 +163,8 @@ public class RedisClusterCollectImplTest {
|
||||
assertEquals(row.getColumns(2), uri2);
|
||||
}
|
||||
}
|
||||
redisClusterClientMockedStatic.close();
|
||||
redisClientMockedStatic.close();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+22
@@ -25,6 +25,8 @@ import org.apache.hertzbeat.common.queue.CommonDataQueue;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* for collector instance
|
||||
* send collect response data by netty
|
||||
@@ -108,4 +110,24 @@ public class NettyDataQueue implements CommonDataQueue {
|
||||
public LogEntry pollLogEntryToStorage() throws InterruptedException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendLogEntryToAlertBatch(List<LogEntry> logEntries) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<LogEntry> pollLogEntryToAlertBatch(int maxBatchSize) throws InterruptedException {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendLogEntryToStorageBatch(List<LogEntry> logEntries) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<LogEntry> pollLogEntryToStorageBatch(int maxBatchSize) throws InterruptedException {
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
||||
+5
@@ -125,6 +125,11 @@ public class CommonProperties {
|
||||
*/
|
||||
private String logEntryToStorageQueueName;
|
||||
|
||||
/**
|
||||
* Timeout for blocking wait in seconds (defaults to 1 second if not configured)
|
||||
*/
|
||||
private Long waitTimeout;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
-5
@@ -97,11 +97,6 @@ public interface CommonConstants {
|
||||
*/
|
||||
String LABEL_ALERT_NAME = "alertname";
|
||||
|
||||
/**
|
||||
* label key: instance host
|
||||
*/
|
||||
String LABEL_INSTANCE_HOST = "instancehost";
|
||||
|
||||
/**
|
||||
* label key: instance name
|
||||
*/
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.common.constants;
|
||||
|
||||
/**
|
||||
* HertzBeat Reserved Keywords Enum
|
||||
*/
|
||||
public enum HertzBeatKeywordsEnum {
|
||||
|
||||
INSTANCE("instance", "metric_instance");
|
||||
|
||||
private final String keyword;
|
||||
|
||||
private final String alias;
|
||||
|
||||
HertzBeatKeywordsEnum(String keyword, String alias) {
|
||||
this.keyword = keyword;
|
||||
this.alias = alias;
|
||||
}
|
||||
|
||||
public String getKeyword() {
|
||||
return keyword;
|
||||
}
|
||||
|
||||
public String getAlias() {
|
||||
return alias;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -38,5 +38,5 @@ public interface MetricDataConstants {
|
||||
String CODE = "code";
|
||||
String MSG = "msg";
|
||||
String INSTANCE_NAME = "instancename";
|
||||
String INSTANCE_HOST = "instancehost";
|
||||
String INSTANCE = "instance";
|
||||
}
|
||||
|
||||
+33
-2
@@ -68,12 +68,13 @@ public class NoticeReceiver {
|
||||
private String name;
|
||||
|
||||
@Schema(title = "Notification information method: 0-SMS 1-Email 2-webhook 3-WeChat Official Account 4-Enterprise WeChat Robot "
|
||||
+ "5-DingTalk Robot 6-FeiShu Robot 7-Telegram Bot 8-SlackWebHook 9-Discord Bot 10-Enterprise WeChat app message",
|
||||
+ "5-DingTalk Robot 6-FeiShu Robot 7-Telegram Bot 8-SlackWebHook 9-Discord Bot 10-Enterprise WeChat app message "
|
||||
+ "11-Slack 12-Discord 13-Gotify 14-FeiShu app message",
|
||||
description = "Notification information method: "
|
||||
+ "0-SMS 1-Email 2-webhook 3-WeChat Official Account "
|
||||
+ "4-Enterprise WeChat Robot 5-DingTalk Robot 6-FeiShu Robot "
|
||||
+ "7-Telegram Bot 8-SlackWebHook 9-Discord Bot 10-Enterprise "
|
||||
+ "WeChat app message",
|
||||
+ "WeChat app message 11-Slack 12-Discord 13-Gotify 14-FeiShu app message",
|
||||
accessMode = READ_WRITE)
|
||||
@Min(0)
|
||||
@NotNull(message = "type can not null")
|
||||
@@ -98,6 +99,20 @@ public class NoticeReceiver {
|
||||
@Column(length = 1000)
|
||||
private String hookUrl;
|
||||
|
||||
@Schema(title = "Auth Type: WebHook Authorization Type",
|
||||
description = "Auth Type: WebHook Authorization Type, one of the 'None', 'Basic' and 'Bearer'",
|
||||
example = "None", accessMode = READ_ONLY)
|
||||
@Size(max = 300)
|
||||
@Column(length = 300)
|
||||
private String hookAuthType;
|
||||
|
||||
@Schema(title = "Auth Token: WebHook Authorization Token",
|
||||
description = "Auth Token: WebHook Authorization Token",
|
||||
example = "YWxpY2U6c3VwZXJtYW4", accessMode = READ_WRITE)
|
||||
@Size(max = 300)
|
||||
@Column(length = 300)
|
||||
private String hookAuthToken;
|
||||
|
||||
@Schema(title = "openId : The notification method is valid for WeChat official account, enterprise WeChat robot or FlyBook robot",
|
||||
description = "openId : The notification method is valid for WeChat official account, enterprise WeChat robot or FlyBook robot",
|
||||
example = "343432", accessMode = READ_WRITE)
|
||||
@@ -105,6 +120,12 @@ public class NoticeReceiver {
|
||||
@Column(length = 300)
|
||||
private String wechatId;
|
||||
|
||||
@Schema(title = "FeiShu app id : The notification method is valid for FeiShu app message",
|
||||
description = "FeiShu app id : The notification method is valid for FeiShu app message",
|
||||
example = "34823984635647", accessMode = READ_WRITE)
|
||||
@Size(max = 255)
|
||||
private String appId;
|
||||
|
||||
@Schema(title = "Access token : The notification method is valid for DingTalk robot",
|
||||
description = "Access token : The notification method is valid for DingTalk robot",
|
||||
example = "34823984635647", accessMode = READ_WRITE)
|
||||
@@ -127,11 +148,21 @@ public class NoticeReceiver {
|
||||
example = "779294123", accessMode = READ_WRITE)
|
||||
private String tgMessageThreadId;
|
||||
|
||||
@Schema(title = "FeiShu app message receiveType: 0-user 1-chat 2-party 3-all",
|
||||
description = "FeiShu app message receiveType: 0-user 1-chat 2-party 3-all",
|
||||
example = "1", accessMode = READ_WRITE)
|
||||
private Byte larkReceiveType;
|
||||
|
||||
@Schema(title = "DingTalk,FeiShu,WeWork user id: The notification method is valid for DingTalk,FeiShu,WeWork Bot",
|
||||
description = "DingTalk,FeiShu,WeWork user id: The notification method is valid for DingTalk,FeiShu,WeWork Bot",
|
||||
example = "779294123", accessMode = READ_WRITE)
|
||||
private String userId;
|
||||
|
||||
@Schema(title = "FeiShu app message chatId: The notification method is valid for FeiShu app message",
|
||||
description = "FeiShu app message chatId: The notification method is valid for FeiShu app message",
|
||||
example = "779294123", accessMode = READ_WRITE)
|
||||
private String chatId;
|
||||
|
||||
@Schema(title = "URL address: The notification method is valid for Slack",
|
||||
description = "URL address: The notification method is valid for Slack",
|
||||
example = "https://hooks.slack.com/services/XXXX/XXXX/XXXX", accessMode = READ_WRITE)
|
||||
|
||||
+5
-4
@@ -18,13 +18,14 @@
|
||||
package org.apache.hertzbeat.common.entity.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Metric History Range Query Data
|
||||
*/
|
||||
@@ -35,8 +36,8 @@ import lombok.NoArgsConstructor;
|
||||
@Schema(description = "Metric History Range Query Data")
|
||||
public class MetricsHistoryData {
|
||||
|
||||
@Schema(title = "Monitoring Task ID")
|
||||
private Long id;
|
||||
@Schema(title = "Monitor Instance (e.g., ip:port or domain)")
|
||||
private String instance;
|
||||
|
||||
@Schema(title = "Monitoring Type")
|
||||
private String app;
|
||||
|
||||
+3
-3
@@ -52,7 +52,7 @@ import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||
@Entity
|
||||
@Table(name = "hzb_monitor", indexes = {
|
||||
@Index(name = "monitor_query_index", columnList = "app"),
|
||||
@Index(name = "monitor_query_index", columnList = "host"),
|
||||
@Index(name = "monitor_query_index", columnList = "instance"),
|
||||
@Index(name = "monitor_query_index", columnList = "name")
|
||||
})
|
||||
@Data
|
||||
@@ -82,10 +82,10 @@ public class Monitor {
|
||||
@Size(max = 100)
|
||||
private String scrape;
|
||||
|
||||
@Schema(title = "peer host: ipv4, ipv6, domain name", example = "192.167.25.11", accessMode = READ_WRITE)
|
||||
@Schema(title = "the monitor target: ip/domain+port or ip/domain", example = "192.167.25.11:8081", accessMode = READ_WRITE)
|
||||
@Size(max = 100)
|
||||
@HostValid
|
||||
private String host;
|
||||
private String instance;
|
||||
|
||||
@Schema(title = "Monitoring of the acquisition interval time in seconds", example = "600", accessMode = READ_WRITE)
|
||||
@Min(10)
|
||||
|
||||
+4
-4
@@ -225,9 +225,9 @@ public final class CollectRep {
|
||||
return metadata.getOrDefault(MetricDataConstants.INSTANCE_NAME, null);
|
||||
}
|
||||
|
||||
public String getInstanceHost() {
|
||||
public String getInstance() {
|
||||
Map<String, String> metadata = getMetadata();
|
||||
return metadata.getOrDefault(MetricDataConstants.INSTANCE_HOST, null);
|
||||
return metadata.getOrDefault(MetricDataConstants.INSTANCE, null);
|
||||
}
|
||||
|
||||
public Map<String, String> getLabels() {
|
||||
@@ -501,8 +501,8 @@ public final class CollectRep {
|
||||
return metadata.getOrDefault(MetricDataConstants.INSTANCE_NAME, null);
|
||||
}
|
||||
|
||||
public String getInstanceHost() {
|
||||
return metadata.getOrDefault(MetricDataConstants.INSTANCE_HOST, null);
|
||||
public String getInstance() {
|
||||
return metadata.getOrDefault(MetricDataConstants.INSTANCE, null);
|
||||
}
|
||||
|
||||
public Map<String, String> getLabels() {
|
||||
|
||||
+4
-4
@@ -37,7 +37,7 @@ import lombok.NoArgsConstructor;
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "hzb_history", indexes = {
|
||||
@Index(name = "history_query_index", columnList = "monitorId"),
|
||||
@Index(name = "history_query_index", columnList = "instance"),
|
||||
@Index(name = "history_query_index", columnList = "app"),
|
||||
@Index(name = "history_query_index", columnList = "metrics"),
|
||||
@Index(name = "history_query_index", columnList = "metric")
|
||||
@@ -54,8 +54,8 @@ public class History {
|
||||
@Schema(description = "Metric data history entity primary key index ID", example = "87584674384", accessMode = READ_ONLY)
|
||||
private Long id;
|
||||
|
||||
@Schema(title = "Monitoring Id", example = "87432674336", accessMode = READ_WRITE)
|
||||
private Long monitorId;
|
||||
@Schema(title = "Monitoring instance", example = "127.0.0.1:8080", accessMode = READ_WRITE)
|
||||
private String instance;
|
||||
|
||||
@Schema(title = "Monitoring Type mysql oracle db2")
|
||||
private String app;
|
||||
@@ -67,7 +67,7 @@ public class History {
|
||||
private String metric;
|
||||
|
||||
@Column(length = 5000)
|
||||
private String instance;
|
||||
private String metricLabels;
|
||||
|
||||
@Schema(title = "Metric Type 0: Number 1:String")
|
||||
private Byte metricType;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
package org.apache.hertzbeat.common.queue;
|
||||
|
||||
import java.util.List;
|
||||
import org.apache.hertzbeat.common.entity.log.LogEntry;
|
||||
import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
|
||||
@@ -91,4 +92,32 @@ public interface CommonDataQueue {
|
||||
* @throws InterruptedException when poll timeout
|
||||
*/
|
||||
LogEntry pollLogEntryToStorage() throws InterruptedException;
|
||||
|
||||
/**
|
||||
* send batch log entries to alert queue
|
||||
* @param logEntries list of log entry data
|
||||
*/
|
||||
void sendLogEntryToAlertBatch(List<LogEntry> logEntries);
|
||||
|
||||
/**
|
||||
* poll batch log entries from alert queue
|
||||
* @param maxBatchSize maximum number of entries to poll
|
||||
* @return list of log entry data
|
||||
* @throws InterruptedException when poll timeout
|
||||
*/
|
||||
List<LogEntry> pollLogEntryToAlertBatch(int maxBatchSize) throws InterruptedException;
|
||||
|
||||
/**
|
||||
* send batch log entries to storage queue
|
||||
* @param logEntries list of log entry data
|
||||
*/
|
||||
void sendLogEntryToStorageBatch(List<LogEntry> logEntries);
|
||||
|
||||
/**
|
||||
* poll batch log entries from storage queue
|
||||
* @param maxBatchSize maximum number of entries to poll
|
||||
* @return list of log entry data
|
||||
* @throws InterruptedException when poll timeout
|
||||
*/
|
||||
List<LogEntry> pollLogEntryToStorageBatch(int maxBatchSize) throws InterruptedException;
|
||||
}
|
||||
|
||||
+45
@@ -17,9 +17,12 @@
|
||||
|
||||
package org.apache.hertzbeat.common.queue.impl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.common.constants.DataQueueConstants;
|
||||
import org.apache.hertzbeat.common.entity.log.LogEntry;
|
||||
@@ -117,6 +120,48 @@ public class InMemoryCommonDataQueue implements CommonDataQueue, DisposableBean
|
||||
return logEntryToStorageQueue.take();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendLogEntryToAlertBatch(List<LogEntry> logEntries) {
|
||||
if (logEntries == null || logEntries.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (LogEntry logEntry : logEntries) {
|
||||
logEntryQueue.offer(logEntry);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<LogEntry> pollLogEntryToAlertBatch(int maxBatchSize) throws InterruptedException {
|
||||
List<LogEntry> batch = new ArrayList<>(maxBatchSize);
|
||||
LogEntry first = logEntryQueue.poll(1, TimeUnit.SECONDS);
|
||||
if (first != null) {
|
||||
batch.add(first);
|
||||
logEntryQueue.drainTo(batch, maxBatchSize - 1);
|
||||
}
|
||||
return batch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendLogEntryToStorageBatch(List<LogEntry> logEntries) {
|
||||
if (logEntries == null || logEntries.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (LogEntry logEntry : logEntries) {
|
||||
logEntryToStorageQueue.offer(logEntry);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<LogEntry> pollLogEntryToStorageBatch(int maxBatchSize) throws InterruptedException {
|
||||
List<LogEntry> batch = new ArrayList<>(maxBatchSize);
|
||||
LogEntry first = logEntryToStorageQueue.poll(1, TimeUnit.SECONDS);
|
||||
if (first != null) {
|
||||
batch.add(first);
|
||||
logEntryToStorageQueue.drainTo(batch, maxBatchSize - 1);
|
||||
}
|
||||
return batch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
metricsDataToAlertQueue.clear();
|
||||
|
||||
+91
-1
@@ -18,8 +18,10 @@
|
||||
package org.apache.hertzbeat.common.queue.impl;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
@@ -33,6 +35,7 @@ import org.apache.hertzbeat.common.serialize.KafkaLogEntryDeserializer;
|
||||
import org.apache.hertzbeat.common.serialize.KafkaLogEntrySerializer;
|
||||
import org.apache.hertzbeat.common.serialize.KafkaMetricsDataDeserializer;
|
||||
import org.apache.hertzbeat.common.serialize.KafkaMetricsDataSerializer;
|
||||
import org.apache.hertzbeat.common.support.exception.CommonDataQueueUnknownException;
|
||||
import org.apache.kafka.clients.consumer.ConsumerConfig;
|
||||
import org.apache.kafka.clients.consumer.ConsumerRecord;
|
||||
import org.apache.kafka.clients.consumer.ConsumerRecords;
|
||||
@@ -117,7 +120,7 @@ public class KafkaCommonDataQueue implements CommonDataQueue, DisposableBean {
|
||||
Map<String, Object> metricsToStorageConsumerConfig = new HashMap<>(consumerConfig);
|
||||
metricsToStorageConsumerConfig.put("group.id", "metrics-persistent-consumer");
|
||||
metricsDataToStorageConsumer = new KafkaConsumer<>(metricsToStorageConsumerConfig, new LongDeserializer(), new KafkaMetricsDataDeserializer());
|
||||
metricsDataToStorageConsumer.subscribe(Collections.singletonList(kafka.getMetricsDataTopic()));
|
||||
metricsDataToStorageConsumer.subscribe(Collections.singletonList(kafka.getMetricsDataToStorageTopic()));
|
||||
|
||||
Map<String, Object> serviceDiscoveryDataConsumerConfig = new HashMap<>(consumerConfig);
|
||||
serviceDiscoveryDataConsumerConfig.put("group.id", "service-discovery-data-consumer");
|
||||
@@ -176,6 +179,7 @@ public class KafkaCommonDataQueue implements CommonDataQueue, DisposableBean {
|
||||
dataConsumer.commitAsync();
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage());
|
||||
throw new CommonDataQueueUnknownException(e.getMessage(), e);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
@@ -259,6 +263,92 @@ public class KafkaCommonDataQueue implements CommonDataQueue, DisposableBean {
|
||||
return genericPollDataFunction(logEntryToStorageQueue, logEntryToStorageConsumer, logEntryToStorageLock);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendLogEntryToAlertBatch(List<LogEntry> logEntries) {
|
||||
if (logEntries == null || logEntries.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (logEntryProducer != null) {
|
||||
try {
|
||||
for (LogEntry logEntry : logEntries) {
|
||||
ProducerRecord<Long, LogEntry> record = new ProducerRecord<>(kafka.getLogEntryDataTopic(), logEntry);
|
||||
logEntryProducer.send(record);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to send LogEntry batch to Kafka: {}", e.getMessage());
|
||||
for (LogEntry logEntry : logEntries) {
|
||||
logEntryQueue.offer(logEntry);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.warn("logEntryProducer is not enabled, using memory queue");
|
||||
for (LogEntry logEntry : logEntries) {
|
||||
logEntryQueue.offer(logEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<LogEntry> pollLogEntryToAlertBatch(int maxBatchSize) throws InterruptedException {
|
||||
return genericBatchPollDataFunction(logEntryQueue, logEntryConsumer, logEntryLock, maxBatchSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendLogEntryToStorageBatch(List<LogEntry> logEntries) {
|
||||
if (logEntries == null || logEntries.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (logEntryProducer != null) {
|
||||
try {
|
||||
for (LogEntry logEntry : logEntries) {
|
||||
ProducerRecord<Long, LogEntry> record = new ProducerRecord<>(kafka.getLogEntryDataToStorageTopic(), logEntry);
|
||||
logEntryProducer.send(record);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to send LogEntry batch to storage via Kafka: {}", e.getMessage());
|
||||
for (LogEntry logEntry : logEntries) {
|
||||
logEntryToStorageQueue.offer(logEntry);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.warn("logEntryProducer is not enabled, using memory queue for storage");
|
||||
for (LogEntry logEntry : logEntries) {
|
||||
logEntryToStorageQueue.offer(logEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<LogEntry> pollLogEntryToStorageBatch(int maxBatchSize) throws InterruptedException {
|
||||
return genericBatchPollDataFunction(logEntryToStorageQueue, logEntryToStorageConsumer, logEntryToStorageLock, maxBatchSize);
|
||||
}
|
||||
|
||||
public <T> List<T> genericBatchPollDataFunction(LinkedBlockingQueue<T> dataQueue, KafkaConsumer<Long, T> dataConsumer,
|
||||
ReentrantLock lock, int maxBatchSize) throws InterruptedException {
|
||||
List<T> batch = new ArrayList<>(maxBatchSize);
|
||||
lock.lockInterruptibly();
|
||||
try {
|
||||
dataQueue.drainTo(batch, maxBatchSize);
|
||||
if (batch.size() >= maxBatchSize) {
|
||||
return batch;
|
||||
}
|
||||
ConsumerRecords<Long, T> records = dataConsumer.poll(Duration.ofSeconds(1));
|
||||
for (ConsumerRecord<Long, T> record : records) {
|
||||
if (batch.size() < maxBatchSize) {
|
||||
batch.add(record.value());
|
||||
} else {
|
||||
dataQueue.offer(record.value());
|
||||
}
|
||||
}
|
||||
dataConsumer.commitAsync();
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage());
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
return batch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
if (metricsDataProducer != null) {
|
||||
|
||||
+82
-30
@@ -17,6 +17,9 @@
|
||||
|
||||
package org.apache.hertzbeat.common.queue.impl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import io.lettuce.core.KeyValue;
|
||||
import io.lettuce.core.RedisClient;
|
||||
import io.lettuce.core.RedisURI;
|
||||
import io.lettuce.core.api.StatefulRedisConnection;
|
||||
@@ -29,10 +32,13 @@ import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
import org.apache.hertzbeat.common.queue.CommonDataQueue;
|
||||
import org.apache.hertzbeat.common.serialize.RedisLogEntryCodec;
|
||||
import org.apache.hertzbeat.common.serialize.RedisMetricsDataCodec;
|
||||
import org.apache.hertzbeat.common.support.exception.CommonDataQueueUnknownException;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* common data queue implement redis.
|
||||
*/
|
||||
@@ -56,6 +62,7 @@ public class RedisCommonDataQueue implements CommonDataQueue, DisposableBean {
|
||||
private final String logEntryQueueName;
|
||||
private final String logEntryToStorageQueueName;
|
||||
private final CommonProperties.RedisProperties redisProperties;
|
||||
private final Long waitTimeout;
|
||||
|
||||
public RedisCommonDataQueue(CommonProperties properties) {
|
||||
|
||||
@@ -83,36 +90,23 @@ public class RedisCommonDataQueue implements CommonDataQueue, DisposableBean {
|
||||
this.metricsDataQueueNameToAlerter = redisProperties.getMetricsDataQueueNameToAlerter();
|
||||
this.logEntryQueueName = redisProperties.getLogEntryQueueName();
|
||||
this.logEntryToStorageQueueName = redisProperties.getLogEntryToStorageQueueName();
|
||||
this.waitTimeout = Objects.requireNonNullElse(redisProperties.getWaitTimeout(), 1L);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CollectRep.MetricsData pollMetricsDataToAlerter() {
|
||||
try {
|
||||
return syncCommands.rpop(metricsDataQueueNameToAlerter);
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage());
|
||||
return null;
|
||||
}
|
||||
public CollectRep.MetricsData pollMetricsDataToAlerter() throws InterruptedException {
|
||||
return genericBlockingPollFunction(metricsDataQueueNameToAlerter, syncCommands);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CollectRep.MetricsData pollMetricsDataToStorage() throws InterruptedException {
|
||||
try {
|
||||
return syncCommands.rpop(metricsDataQueueNameToStorage);
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage());
|
||||
return null;
|
||||
}
|
||||
return genericBlockingPollFunction(metricsDataQueueNameToStorage, syncCommands);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public CollectRep.MetricsData pollServiceDiscoveryData() throws InterruptedException {
|
||||
try {
|
||||
return syncCommands.rpop(metricsDataQueueNameForServiceDiscovery);
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage());
|
||||
return null;
|
||||
}
|
||||
return genericBlockingPollFunction(metricsDataQueueNameForServiceDiscovery, syncCommands);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -153,12 +147,7 @@ public class RedisCommonDataQueue implements CommonDataQueue, DisposableBean {
|
||||
|
||||
@Override
|
||||
public LogEntry pollLogEntry() throws InterruptedException {
|
||||
try {
|
||||
return logEntrySyncCommands.rpop(logEntryQueueName);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to poll LogEntry from Redis: {}", e.getMessage());
|
||||
throw new InterruptedException("Failed to poll LogEntry from Redis");
|
||||
}
|
||||
return genericBlockingPollFunction(logEntryQueueName, logEntrySyncCommands);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -172,12 +161,43 @@ public class RedisCommonDataQueue implements CommonDataQueue, DisposableBean {
|
||||
|
||||
@Override
|
||||
public LogEntry pollLogEntryToStorage() throws InterruptedException {
|
||||
try {
|
||||
return logEntrySyncCommands.rpop(logEntryToStorageQueueName);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to poll LogEntry from storage via Redis: {}", e.getMessage());
|
||||
throw new InterruptedException("Failed to poll LogEntry from storage via Redis");
|
||||
return genericBlockingPollFunction(logEntryToStorageQueueName, logEntrySyncCommands);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void sendLogEntryToAlertBatch(List<LogEntry> logEntries) {
|
||||
if (logEntries == null || logEntries.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
logEntrySyncCommands.lpush(logEntryQueueName, logEntries.toArray(new LogEntry[0]));
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to send LogEntry batch to Redis: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<LogEntry> pollLogEntryToAlertBatch(int maxBatchSize) throws InterruptedException {
|
||||
return genericBatchPollFunction(logEntryQueueName, logEntrySyncCommands, maxBatchSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void sendLogEntryToStorageBatch(List<LogEntry> logEntries) {
|
||||
if (logEntries == null || logEntries.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
logEntrySyncCommands.lpush(logEntryToStorageQueueName, logEntries.toArray(new LogEntry[0]));
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to send LogEntry batch to storage via Redis: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<LogEntry> pollLogEntryToStorageBatch(int maxBatchSize) throws InterruptedException {
|
||||
return genericBatchPollFunction(logEntryToStorageQueueName, logEntrySyncCommands, maxBatchSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -187,4 +207,36 @@ public class RedisCommonDataQueue implements CommonDataQueue, DisposableBean {
|
||||
redisClient.shutdown();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private <T> T genericBlockingPollFunction(String key, RedisCommands<String, T> commands) throws InterruptedException {
|
||||
try {
|
||||
// Use BRPOP for blocking pop with the configured timeout.
|
||||
// If data arrives, it returns immediately; if it times out, it returns null.
|
||||
KeyValue<String, T> keyData = commands.brpop(waitTimeout, key);
|
||||
if (keyData != null) {
|
||||
return keyData.getValue();
|
||||
} else {
|
||||
// Returns null on timeout
|
||||
return null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Redis BRPOP failed: {}", e.getMessage());
|
||||
throw new CommonDataQueueUnknownException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private List<LogEntry> genericBatchPollFunction(String key, RedisCommands<String, LogEntry> commands, int maxBatchSize) {
|
||||
List<LogEntry> batch = new ArrayList<>(maxBatchSize);
|
||||
try {
|
||||
List<LogEntry> elements = commands.rpop(key, maxBatchSize);
|
||||
if (elements != null) {
|
||||
batch.addAll(elements);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Redis batch poll failed: {}", e.getMessage());
|
||||
}
|
||||
return batch;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.common.support.exception;
|
||||
|
||||
/**
|
||||
* Common data queue unknown exception
|
||||
*/
|
||||
public class CommonDataQueueUnknownException extends RuntimeException {
|
||||
|
||||
public CommonDataQueueUnknownException() {
|
||||
}
|
||||
|
||||
public CommonDataQueueUnknownException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public CommonDataQueueUnknownException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public CommonDataQueueUnknownException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
+15
-1
@@ -44,7 +44,21 @@ public class HostParamValidator implements ConstraintValidator<HostValid, String
|
||||
value = value.replaceFirst(PATTERN_HTTPS, BLANK);
|
||||
}
|
||||
|
||||
return IpDomainUtil.validateIpDomain(value);
|
||||
String hostPart = value;
|
||||
|
||||
if (value.contains(":")) {
|
||||
// if contains multiple ":", it may be IPv6 with port
|
||||
if (value.lastIndexOf(":") > value.indexOf(":") && value.contains("[")) {
|
||||
int portIndex = value.lastIndexOf(":");
|
||||
hostPart = value.substring(0, portIndex);
|
||||
} else if (value.split(":").length == 2) {
|
||||
// it is IPv4 or domain with port
|
||||
String[] parts = value.split(":");
|
||||
hostPart = parts[0];
|
||||
}
|
||||
}
|
||||
|
||||
return IpDomainUtil.validateIpDomain(hostPart);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.common.util;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Backoff utility class.
|
||||
*/
|
||||
public final class BackoffUtils {
|
||||
|
||||
/** Private constructor to prevent instantiation */
|
||||
private BackoffUtils() {}
|
||||
|
||||
/**
|
||||
* Sleeps for the next delay specified by the ExponentialBackoff instance.
|
||||
* If the thread is interrupted during sleep, it resets the interrupt status
|
||||
* and returns false to indicate that the operation should not continue.
|
||||
*
|
||||
* @param backoff the ExponentialBackoff instance to get the next delay from
|
||||
* @return true if the sleep completed without interruption, false otherwise
|
||||
*/
|
||||
public static boolean shouldContinueAfterBackoff(ExponentialBackoff backoff) {
|
||||
if (Thread.currentThread().isInterrupted()) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
TimeUnit.MILLISECONDS.sleep(backoff.nextDelay());
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.common.util;
|
||||
|
||||
/**
|
||||
* Exponential backoff utility class.
|
||||
*
|
||||
* <p>Provides exponentially increasing delays starting from an initial value,
|
||||
* doubling with each call to {@link #nextDelay()} until reaching the maximum value.
|
||||
* Call {@link #reset()} to restart from the initial delay.</p>
|
||||
*
|
||||
* <p>Note: This class is <b>not</b> thread-safe. Each thread should use its own instance.</p>
|
||||
*/
|
||||
public final class ExponentialBackoff {
|
||||
private final long initial;
|
||||
private final long max;
|
||||
private long current;
|
||||
|
||||
public ExponentialBackoff(long initial, long max) {
|
||||
if (initial <= 0 || max < initial) {
|
||||
throw new IllegalArgumentException("Invalid exponential backoff params");
|
||||
}
|
||||
this.initial = initial;
|
||||
this.max = max;
|
||||
this.current = initial;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current delay value and advances to the next delay.
|
||||
* The delay doubles on each call until it reaches the maximum value.
|
||||
*
|
||||
* @return the delay in milliseconds to wait before the next retry
|
||||
*/
|
||||
public long nextDelay() {
|
||||
long delay = this.current;
|
||||
if (this.current <= this.max / 2) {
|
||||
this.current = this.current * 2;
|
||||
} else {
|
||||
this.current = this.max;
|
||||
}
|
||||
return delay;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the backoff to the initial delay value.
|
||||
* Should be called when an operation succeeds after previous failures.
|
||||
*/
|
||||
public void reset() {
|
||||
this.current = this.initial;
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.common.util;
|
||||
|
||||
import org.apache.hertzbeat.common.constants.HertzBeatKeywordsEnum;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Validate JEXL rules
|
||||
*/
|
||||
public class HertzBeatKeywordsUtil {
|
||||
|
||||
|
||||
private static final Map<String, HertzBeatKeywordsEnum> KEYWORDS =
|
||||
Arrays.stream(HertzBeatKeywordsEnum.values())
|
||||
.collect(Collectors.toMap(HertzBeatKeywordsEnum::getKeyword, e -> e));
|
||||
|
||||
|
||||
/**
|
||||
* Verify if the field matches any reserved keywords
|
||||
*
|
||||
* @param field Field name to verify
|
||||
*/
|
||||
public static void verifyKeywords(String field) {
|
||||
HertzBeatKeywordsEnum keyword = KEYWORDS.get(field);
|
||||
if (null == keyword) {
|
||||
return;
|
||||
}
|
||||
throw new IllegalArgumentException(
|
||||
String.format("Field matches keyword `%s`, please set alias `%s`.",
|
||||
keyword.getKeyword(), keyword.getAlias()));
|
||||
}
|
||||
}
|
||||
+11
-7
@@ -27,6 +27,7 @@ import io.lettuce.core.RedisClient;
|
||||
import io.lettuce.core.RedisURI;
|
||||
import io.lettuce.core.api.StatefulRedisConnection;
|
||||
import io.lettuce.core.api.sync.RedisCommands;
|
||||
import io.lettuce.core.KeyValue;
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -78,7 +79,7 @@ class RedisCommonDataQueueTest {
|
||||
when(redisProperties.getLogEntryToStorageQueueName()).thenReturn("logEntryToStorageQueue");
|
||||
when(redisProperties.getRedisHost()).thenReturn("localhost");
|
||||
when(redisProperties.getRedisPort()).thenReturn(6379);
|
||||
|
||||
when(redisProperties.getWaitTimeout()).thenReturn(1L);
|
||||
try (MockedStatic<RedisClient> mockedRedisClient = mockStatic(RedisClient.class)) {
|
||||
mockedRedisClient.when(() -> RedisClient.create(any(RedisURI.class))).thenReturn(redisClient);
|
||||
when(redisClient.connect(any(RedisMetricsDataCodec.class))).thenReturn(connection);
|
||||
@@ -95,11 +96,13 @@ class RedisCommonDataQueueTest {
|
||||
CollectRep.MetricsData metricsData = CollectRep.MetricsData.newBuilder()
|
||||
.setMetrics("test metrics")
|
||||
.build();
|
||||
String queueName = "metricsDataQueueToAlerter";
|
||||
|
||||
when(syncCommands.rpop("metricsDataQueueToAlerter")).thenReturn(metricsData);
|
||||
when(syncCommands.brpop(1L, queueName)).thenReturn(KeyValue.just(queueName, metricsData));
|
||||
|
||||
CollectRep.MetricsData actualMetricsData = redisCommonDataQueue.pollMetricsDataToAlerter();
|
||||
assertEquals(metricsData, actualMetricsData);
|
||||
verify(syncCommands).brpop(1L, queueName);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -163,13 +166,14 @@ class RedisCommonDataQueueTest {
|
||||
.severityText("WARN")
|
||||
.body("Test warning log message")
|
||||
.build();
|
||||
String queueName = "logEntryQueue";
|
||||
|
||||
when(logEntrySyncCommands.rpop("logEntryQueue")).thenReturn(expectedLogEntry);
|
||||
when(logEntrySyncCommands.brpop(1L, queueName)).thenReturn(KeyValue.just(queueName, expectedLogEntry));
|
||||
|
||||
LogEntry result = redisCommonDataQueue.pollLogEntry();
|
||||
|
||||
assertEquals(expectedLogEntry, result);
|
||||
verify(logEntrySyncCommands).rpop("logEntryQueue");
|
||||
verify(logEntrySyncCommands).brpop(1L, "logEntryQueue");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -181,13 +185,13 @@ class RedisCommonDataQueueTest {
|
||||
.severityText("FATAL")
|
||||
.body("Critical error log for storage")
|
||||
.build();
|
||||
String queueName = "logEntryToStorageQueue";
|
||||
|
||||
when(logEntrySyncCommands.rpop("logEntryToStorageQueue")).thenReturn(expectedLogEntry);
|
||||
|
||||
when(logEntrySyncCommands.brpop(1L, queueName)).thenReturn(KeyValue.just(queueName, expectedLogEntry));
|
||||
LogEntry result = redisCommonDataQueue.pollLogEntryToStorage();
|
||||
|
||||
assertEquals(expectedLogEntry, result);
|
||||
verify(logEntrySyncCommands).rpop("logEntryToStorageQueue");
|
||||
verify(logEntrySyncCommands).brpop(1L, "logEntryToStorageQueue");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.common.util;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/**
|
||||
* Test case for {@link BackoffUtils}
|
||||
*/
|
||||
class BackoffUtilsTest {
|
||||
|
||||
@Test
|
||||
void shouldContinueAfterBackoff() {
|
||||
ExponentialBackoff backoff = new ExponentialBackoff(10L, 100L);
|
||||
boolean shouldContinue = BackoffUtils.shouldContinueAfterBackoff(backoff);
|
||||
assertTrue(shouldContinue);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotContinueWhenInterrupted() {
|
||||
Thread.currentThread().interrupt();
|
||||
ExponentialBackoff backoff = new ExponentialBackoff(10L, 100L);
|
||||
boolean shouldContinue = BackoffUtils.shouldContinueAfterBackoff(backoff);
|
||||
assertFalse(shouldContinue);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHandleInterruptedException() throws InterruptedException {
|
||||
final Thread mainThread = Thread.currentThread();
|
||||
ExponentialBackoff backoff = new ExponentialBackoff(1000L, 2000L);
|
||||
|
||||
Thread interruptingThread = new Thread(() -> {
|
||||
try {
|
||||
// Give the main thread some time to enter the sleep
|
||||
Thread.sleep(200);
|
||||
mainThread.interrupt();
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
});
|
||||
|
||||
interruptingThread.start();
|
||||
|
||||
boolean shouldContinue = BackoffUtils.shouldContinueAfterBackoff(backoff);
|
||||
|
||||
interruptingThread.join();
|
||||
|
||||
assertFalse(shouldContinue);
|
||||
assertTrue(Thread.currentThread().isInterrupted());
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.common.util;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Test case for {@link ExponentialBackoff}
|
||||
*/
|
||||
class ExponentialBackoffTest {
|
||||
|
||||
@Test
|
||||
void testProgressionAndCap() {
|
||||
ExponentialBackoff backoff = new ExponentialBackoff(50L, 1000L);
|
||||
Assertions.assertEquals(50L, backoff.nextDelay());
|
||||
Assertions.assertEquals(100L, backoff.nextDelay());
|
||||
Assertions.assertEquals(200L, backoff.nextDelay());
|
||||
Assertions.assertEquals(400L, backoff.nextDelay());
|
||||
Assertions.assertEquals(800L, backoff.nextDelay());
|
||||
Assertions.assertEquals(1000L, backoff.nextDelay());
|
||||
Assertions.assertEquals(1000L, backoff.nextDelay());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testReset() {
|
||||
ExponentialBackoff backoff = new ExponentialBackoff(50L, 1000L);
|
||||
Assertions.assertEquals(50L, backoff.nextDelay());
|
||||
Assertions.assertEquals(100L, backoff.nextDelay());
|
||||
backoff.reset();
|
||||
Assertions.assertEquals(50L, backoff.nextDelay());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInvalidParams() {
|
||||
Assertions.assertThrows(IllegalArgumentException.class, () -> new ExponentialBackoff(0L, 1000L));
|
||||
Assertions.assertThrows(IllegalArgumentException.class, () -> new ExponentialBackoff(50L, 10L));
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.common.util;
|
||||
|
||||
import org.apache.hertzbeat.common.constants.HertzBeatKeywordsEnum;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
/**
|
||||
* Test case for {@link HertzBeatKeywordsUtil}
|
||||
*/
|
||||
class HertzBeatKeywordsUtilTest {
|
||||
|
||||
@Test
|
||||
void testVerifyKeywords_WithReservedKeyword_Instance() {
|
||||
IllegalArgumentException exception = assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> HertzBeatKeywordsUtil.verifyKeywords("instance")
|
||||
);
|
||||
String expectedMessage = String.format(
|
||||
"Field matches keyword `%s`, please set alias `%s`.",
|
||||
HertzBeatKeywordsEnum.INSTANCE.getKeyword(),
|
||||
HertzBeatKeywordsEnum.INSTANCE.getAlias()
|
||||
);
|
||||
assertEquals(expectedMessage, exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testVerifyKeywords_WithValidField() {
|
||||
assertDoesNotThrow(() -> HertzBeatKeywordsUtil.verifyKeywords("cpu_usage"));
|
||||
assertDoesNotThrow(() -> HertzBeatKeywordsUtil.verifyKeywords("memory_used"));
|
||||
assertDoesNotThrow(() -> HertzBeatKeywordsUtil.verifyKeywords("response_time"));
|
||||
assertDoesNotThrow(() -> HertzBeatKeywordsUtil.verifyKeywords("metric_instance"));
|
||||
assertDoesNotThrow(() -> HertzBeatKeywordsUtil.verifyKeywords(HertzBeatKeywordsEnum.INSTANCE.getAlias()));
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -102,7 +102,7 @@ public class LogPeriodicAlertE2eTest {
|
||||
@DynamicPropertySource
|
||||
static void greptimeProps(DynamicPropertyRegistry r) {
|
||||
// Configure GreptimeDB storage
|
||||
r.add("warehouse.store.jpa.enabled", () -> "false");
|
||||
r.add("warehouse.store.duckdb.enabled", () -> "false");
|
||||
r.add("warehouse.store.greptime.enabled", () -> "true");
|
||||
r.add("warehouse.store.greptime.http-endpoint", () -> "http://localhost:" + greptimedb.getMappedPort(GREPTIME_HTTP_PORT));
|
||||
r.add("warehouse.store.greptime.grpc-endpoints", () -> "localhost:" + greptimedb.getMappedPort(GREPTIME_GRPC_PORT));
|
||||
|
||||
+1
-1
@@ -86,7 +86,7 @@ public class GreptimeLogStorageE2eTest {
|
||||
|
||||
@DynamicPropertySource
|
||||
static void greptimeProps(DynamicPropertyRegistry r) {
|
||||
r.add("warehouse.store.jpa.enabled", () -> "false");
|
||||
r.add("warehouse.store.duckdb.enabled", () -> "false");
|
||||
r.add("warehouse.store.greptime.enabled", () -> "true");
|
||||
r.add("warehouse.store.greptime.http-endpoint", () -> "http://localhost:" + greptimedb.getMappedPort(GREPTIME_HTTP_PORT));
|
||||
r.add("warehouse.store.greptime.grpc-endpoints", () -> "localhost:" + greptimedb.getMappedPort(GREPTIME_GRPC_PORT));
|
||||
|
||||
@@ -28,6 +28,10 @@
|
||||
<artifactId>hertzbeat-log</artifactId>
|
||||
<name>${project.artifactId}</name>
|
||||
|
||||
<properties>
|
||||
<awaitility.version>4.2.0</awaitility.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- common -->
|
||||
<dependency>
|
||||
@@ -67,6 +71,13 @@
|
||||
<groupId>com.google.protobuf</groupId>
|
||||
<artifactId>protobuf-java-util</artifactId>
|
||||
</dependency>
|
||||
<!-- awaitility for async testing -->
|
||||
<dependency>
|
||||
<groupId>org.awaitility</groupId>
|
||||
<artifactId>awaitility</artifactId>
|
||||
<version>${awaitility.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
+1
-1
@@ -60,7 +60,7 @@ public class LogIngestionController {
|
||||
@PostMapping("/ingest/{protocol}")
|
||||
public ResponseEntity<Message<Void>> ingestExternLog(@PathVariable("protocol") String protocol,
|
||||
@RequestBody String content) {
|
||||
log.info("Receive extern log from protocol: {}, content length: {}", protocol, content == null ? 0 : content.length());
|
||||
log.debug("Receive extern log from protocol: {}, content length: {}", protocol, content == null ? 0 : content.length());
|
||||
if (!StringUtils.hasText(protocol)) {
|
||||
protocol = DEFAULT_PROTOCOL; // Default to OTLP if no protocol specified
|
||||
}
|
||||
|
||||
@@ -19,34 +19,75 @@
|
||||
|
||||
package org.apache.hertzbeat.log.notice;
|
||||
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.common.entity.log.LogEntry;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* SSE manager for log
|
||||
* SSE manager for log with batch processing support for high TPS scenarios
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
@Getter
|
||||
public class LogSseManager {
|
||||
|
||||
private static final long BATCH_INTERVAL_MS = 200;
|
||||
private static final int MAX_BATCH_SIZE = 1000;
|
||||
private static final int MAX_QUEUE_SIZE = 10000;
|
||||
|
||||
private final Map<Long, SseSubscriber> emitters = new ConcurrentHashMap<>();
|
||||
private final Queue<LogEntry> logQueue = new ConcurrentLinkedQueue<>();
|
||||
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(r -> {
|
||||
Thread t = new Thread(r, "sse-batch-scheduler");
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
});
|
||||
private final ExecutorService senderPool = Executors.newCachedThreadPool(r -> {
|
||||
Thread t = new Thread(r, "sse-sender");
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
});
|
||||
private final AtomicLong queueSize = new AtomicLong(0);
|
||||
|
||||
public LogSseManager() {
|
||||
scheduler.scheduleAtFixedRate(this::flushBatch, BATCH_INTERVAL_MS, BATCH_INTERVAL_MS, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void shutdown() {
|
||||
scheduler.shutdown();
|
||||
senderPool.shutdown();
|
||||
try {
|
||||
scheduler.awaitTermination(2, TimeUnit.SECONDS);
|
||||
senderPool.awaitTermination(2, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
scheduler.shutdownNow();
|
||||
senderPool.shutdownNow();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new SSE emitter for a client with specified filters
|
||||
* @param clientId The unique identifier for the client
|
||||
* @param filters The filters to apply to the log data
|
||||
* @return The SSE emitter
|
||||
*/
|
||||
public SseEmitter createEmitter(Long clientId, LogSseFilterCriteria filters) {
|
||||
SseEmitter emitter = new SseEmitter(Long.MAX_VALUE);
|
||||
@@ -54,55 +95,115 @@ public class LogSseManager {
|
||||
emitter.onTimeout(() -> removeEmitter(clientId));
|
||||
emitter.onError((ex) -> removeEmitter(clientId));
|
||||
|
||||
SseSubscriber subscriber = new SseSubscriber(emitter, filters);
|
||||
emitters.put(clientId, subscriber);
|
||||
emitters.put(clientId, new SseSubscriber(emitter, filters));
|
||||
return emitter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast log data to all subscribers
|
||||
* @param logEntry The log data to broadcast
|
||||
* Queue log entry for batch processing
|
||||
*/
|
||||
@Async
|
||||
public void broadcast(LogEntry logEntry) {
|
||||
emitters.forEach((clientId, subscriber) -> {
|
||||
try {
|
||||
// Check if the log entry matches the subscriber's filter criteria
|
||||
if (subscriber.filters == null || subscriber.filters.matches(logEntry)) {
|
||||
subscriber.emitter.send(SseEmitter.event()
|
||||
.id(String.valueOf(System.currentTimeMillis()))
|
||||
.name("LOG_EVENT")
|
||||
.data(logEntry));
|
||||
}
|
||||
} catch (IOException | IllegalStateException e) {
|
||||
subscriber.emitter.complete();
|
||||
removeEmitter(clientId);
|
||||
} catch (Exception exception) {
|
||||
log.error("Failed to broadcast log to client: {}", exception.getMessage());
|
||||
subscriber.emitter.complete();
|
||||
removeEmitter(clientId);
|
||||
if (queueSize.incrementAndGet() > MAX_QUEUE_SIZE) {
|
||||
queueSize.decrementAndGet();
|
||||
return;
|
||||
}
|
||||
boolean offered = logQueue.offer(logEntry);
|
||||
if (!offered) {
|
||||
queueSize.decrementAndGet();
|
||||
log.warn("Failed to enqueue log entry: {}", logEntry);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush queued logs to all subscribers in batch
|
||||
*/
|
||||
private void flushBatch() {
|
||||
try {
|
||||
if (logQueue.isEmpty() || emitters.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
List<LogEntry> batch = new ArrayList<>(MAX_BATCH_SIZE);
|
||||
LogEntry entry;
|
||||
while (batch.size() < MAX_BATCH_SIZE && (entry = logQueue.poll()) != null) {
|
||||
batch.add(entry);
|
||||
queueSize.decrementAndGet();
|
||||
}
|
||||
|
||||
if (batch.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Send to each subscriber in parallel
|
||||
for (Map.Entry<Long, SseSubscriber> e : emitters.entrySet()) {
|
||||
Long clientId = e.getKey();
|
||||
SseSubscriber subscriber = e.getValue();
|
||||
List<LogEntry> filtered = filterLogs(batch, subscriber.filters);
|
||||
if (!filtered.isEmpty()) {
|
||||
senderPool.submit(() -> sendToSubscriber(clientId, subscriber.emitter, filtered));
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Error in flushBatch: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendToSubscriber(Long clientId, SseEmitter emitter, List<LogEntry> logs) {
|
||||
try {
|
||||
long batchTimestamp = System.currentTimeMillis();
|
||||
int sequenceNumber = 0;
|
||||
for (LogEntry logEntry : logs) {
|
||||
String eventId = batchTimestamp + "-" + sequenceNumber++;
|
||||
emitter.send(SseEmitter.event()
|
||||
.id(eventId)
|
||||
.name("LOG_EVENT")
|
||||
.data(logEntry));
|
||||
}
|
||||
} catch (IOException | IllegalStateException e) {
|
||||
safeComplete(clientId, emitter);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to send to client {}: {}", clientId, e.getMessage());
|
||||
safeComplete(clientId, emitter);
|
||||
}
|
||||
}
|
||||
|
||||
private void safeComplete(Long clientId, SseEmitter emitter) {
|
||||
try {
|
||||
emitter.complete();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
removeEmitter(clientId);
|
||||
}
|
||||
|
||||
private List<LogEntry> filterLogs(List<LogEntry> logs, LogSseFilterCriteria filters) {
|
||||
if (filters == null) {
|
||||
return logs;
|
||||
}
|
||||
List<LogEntry> filtered = new ArrayList<>();
|
||||
for (LogEntry log : logs) {
|
||||
if (filters.matches(log)) {
|
||||
filtered.add(log);
|
||||
}
|
||||
}
|
||||
return filtered;
|
||||
}
|
||||
|
||||
private void removeEmitter(Long clientId) {
|
||||
emitters.remove(clientId);
|
||||
}
|
||||
|
||||
|
||||
public long getQueueSize() {
|
||||
return queueSize.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* SSE subscriber
|
||||
* SseSubscriber for SseEmitter and LogSseFilterCriteria
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public static class SseSubscriber {
|
||||
/**
|
||||
* The SSE emitter for streaming log events
|
||||
*/
|
||||
private SseEmitter emitter;
|
||||
/**
|
||||
* The filters for streaming log events
|
||||
*/
|
||||
private LogSseFilterCriteria filters;
|
||||
}
|
||||
}
|
||||
|
||||
+3
-7
@@ -69,13 +69,9 @@ public class OtlpLogProtocolAdapter implements LogProtocolAdapter {
|
||||
// Extract LogEntry instances from the request
|
||||
List<LogEntry> logEntries = extractLogEntries(request);
|
||||
log.debug("Successfully extracted {} log entries from OTLP payload {}", logEntries.size(), content);
|
||||
|
||||
logEntries.forEach(entry -> {
|
||||
commonDataQueue.sendLogEntry(entry);
|
||||
logSseManager.broadcast(entry);
|
||||
log.info("Log entry sent to queue: {}", entry);
|
||||
});
|
||||
|
||||
commonDataQueue.sendLogEntryToStorageBatch(logEntries);
|
||||
commonDataQueue.sendLogEntryToAlertBatch(logEntries);
|
||||
logEntries.forEach(logSseManager::broadcast);
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
log.error("Failed to parse OTLP log payload: {}", e.getMessage());
|
||||
throw new IllegalArgumentException("Invalid OTLP log content", e);
|
||||
|
||||
+37
-15
@@ -20,19 +20,21 @@
|
||||
package org.apache.hertzbeat.log.notice;
|
||||
|
||||
import org.apache.hertzbeat.common.entity.log.LogEntry;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Spy;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.awaitility.Awaitility.await;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
@@ -41,14 +43,21 @@ import static org.mockito.Mockito.verify;
|
||||
/**
|
||||
* Unit tests for {@link LogSseManager}.
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class LogSseManagerTest {
|
||||
|
||||
@Spy
|
||||
private LogSseManager logSseManager;
|
||||
|
||||
private static final Long CLIENT_ID = 1L;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
logSseManager = new LogSseManager();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
logSseManager.shutdown();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCreateAndStoreEmitter() {
|
||||
// When: Creating a new emitter for a client
|
||||
@@ -73,12 +82,14 @@ class LogSseManagerTest {
|
||||
// When: An "INFO" log is broadcast
|
||||
logSseManager.broadcast(infoLog);
|
||||
|
||||
// Then: The log should be sent to the client
|
||||
verify(mockEmitter).send(any(SseEmitter.SseEventBuilder.class));
|
||||
// Then: The log should be sent to the client (wait for batch processing)
|
||||
await().atMost(500, TimeUnit.MILLISECONDS).untilAsserted(() ->
|
||||
verify(mockEmitter, atLeastOnce()).send(any(SseEmitter.SseEventBuilder.class))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotBroadcastLogWhenFilterDoesNotMatch() throws IOException {
|
||||
void shouldNotBroadcastLogWhenFilterDoesNotMatch() throws IOException, InterruptedException {
|
||||
// Given: A client with a filter for "ERROR" logs
|
||||
LogSseFilterCriteria filters = new LogSseFilterCriteria();
|
||||
filters.setSeverityText("ERROR");
|
||||
@@ -90,6 +101,9 @@ class LogSseManagerTest {
|
||||
// When: An "INFO" log is broadcast
|
||||
logSseManager.broadcast(infoLog);
|
||||
|
||||
// Wait for batch processing
|
||||
Thread.sleep(300);
|
||||
|
||||
// Then: The log should NOT be sent to the client
|
||||
verify(mockEmitter, never()).send(any(SseEmitter.SseEventBuilder.class));
|
||||
}
|
||||
@@ -105,12 +119,14 @@ class LogSseManagerTest {
|
||||
// When: Any log is broadcast
|
||||
logSseManager.broadcast(anyLog);
|
||||
|
||||
// Then: The log should be sent to the client
|
||||
verify(mockEmitter).send(any(SseEmitter.SseEventBuilder.class));
|
||||
// Then: The log should be sent to the client (wait for batch processing)
|
||||
await().atMost(500, TimeUnit.MILLISECONDS).untilAsserted(() ->
|
||||
verify(mockEmitter, atLeastOnce()).send(any(SseEmitter.SseEventBuilder.class))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldBroadcastOnlyToMatchingSubscribers() throws IOException {
|
||||
void shouldBroadcastOnlyToMatchingSubscribers() throws IOException, InterruptedException {
|
||||
// Given: Two clients with different filters
|
||||
LogSseFilterCriteria infoFilter = new LogSseFilterCriteria();
|
||||
infoFilter.setSeverityText("INFO");
|
||||
@@ -127,8 +143,12 @@ class LogSseManagerTest {
|
||||
// When: An "INFO" log is broadcast
|
||||
logSseManager.broadcast(infoLog);
|
||||
|
||||
// Wait for batch processing
|
||||
await().atMost(500, TimeUnit.MILLISECONDS).untilAsserted(() ->
|
||||
verify(infoEmitter, atLeastOnce()).send(any(SseEmitter.SseEventBuilder.class))
|
||||
);
|
||||
|
||||
// Then: The log is sent only to the client subscribed to "INFO" logs
|
||||
verify(infoEmitter).send(any(SseEmitter.SseEventBuilder.class));
|
||||
verify(errorEmitter, never()).send(any(SseEmitter.SseEventBuilder.class));
|
||||
}
|
||||
|
||||
@@ -146,8 +166,10 @@ class LogSseManagerTest {
|
||||
logSseManager.broadcast(log);
|
||||
|
||||
// Then: The failing emitter should be completed and removed
|
||||
verify(mockEmitter).complete();
|
||||
assertFalse(logSseManager.getEmitters().containsKey(CLIENT_ID));
|
||||
await().atMost(500, TimeUnit.MILLISECONDS).untilAsserted(() -> {
|
||||
verify(mockEmitter).complete();
|
||||
assertFalse(logSseManager.getEmitters().containsKey(CLIENT_ID));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+39
-17
@@ -44,6 +44,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyList;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
@@ -85,12 +86,16 @@ class OtlpLogProtocolAdapterTest {
|
||||
|
||||
adapter.ingest(otlpPayload);
|
||||
|
||||
ArgumentCaptor<LogEntry> logEntryCaptor = ArgumentCaptor.forClass(LogEntry.class);
|
||||
verify(commonDataQueue, times(1)).sendLogEntry(logEntryCaptor.capture());
|
||||
verify(logSseManager, times(1)).broadcast(logEntryCaptor.capture());
|
||||
ArgumentCaptor<List<LogEntry>> listCaptor = ArgumentCaptor.forClass(List.class);
|
||||
verify(commonDataQueue, times(1)).sendLogEntryToStorageBatch(listCaptor.capture());
|
||||
verify(commonDataQueue, times(1)).sendLogEntryToAlertBatch(anyList());
|
||||
verify(logSseManager, times(1)).broadcast(any(LogEntry.class));
|
||||
|
||||
LogEntry capturedEntry = logEntryCaptor.getValue();
|
||||
assertNotNull(capturedEntry);
|
||||
List<LogEntry> capturedList = listCaptor.getValue();
|
||||
assertNotNull(capturedList);
|
||||
assertEquals(1, capturedList.size());
|
||||
|
||||
LogEntry capturedEntry = capturedList.get(0);
|
||||
assertEquals("test-service", capturedEntry.getResource().get("service_name"));
|
||||
assertEquals("test-version", capturedEntry.getResource().get("service_version"));
|
||||
assertEquals("test-scope", capturedEntry.getInstrumentationScope().getName());
|
||||
@@ -106,8 +111,13 @@ class OtlpLogProtocolAdapterTest {
|
||||
|
||||
adapter.ingest(otlpPayload);
|
||||
|
||||
verify(commonDataQueue, times(2)).sendLogEntry(any(LogEntry.class));
|
||||
ArgumentCaptor<List<LogEntry>> listCaptor = ArgumentCaptor.forClass(List.class);
|
||||
verify(commonDataQueue, times(1)).sendLogEntryToStorageBatch(listCaptor.capture());
|
||||
verify(commonDataQueue, times(1)).sendLogEntryToAlertBatch(anyList());
|
||||
verify(logSseManager, times(2)).broadcast(any(LogEntry.class));
|
||||
|
||||
List<LogEntry> capturedList = listCaptor.getValue();
|
||||
assertEquals(2, capturedList.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -116,15 +126,16 @@ class OtlpLogProtocolAdapterTest {
|
||||
|
||||
adapter.ingest(otlpPayload);
|
||||
|
||||
verify(commonDataQueue, times(1)).sendLogEntry(any(LogEntry.class));
|
||||
ArgumentCaptor<List<LogEntry>> listCaptor = ArgumentCaptor.forClass(List.class);
|
||||
verify(commonDataQueue, times(1)).sendLogEntryToStorageBatch(listCaptor.capture());
|
||||
verify(commonDataQueue, times(1)).sendLogEntryToAlertBatch(anyList());
|
||||
verify(logSseManager, times(1)).broadcast(any(LogEntry.class));
|
||||
|
||||
ArgumentCaptor<LogEntry> logEntryCaptor = ArgumentCaptor.forClass(LogEntry.class);
|
||||
verify(commonDataQueue).sendLogEntry(logEntryCaptor.capture());
|
||||
|
||||
LogEntry capturedEntry = logEntryCaptor.getValue();
|
||||
assertNotNull(capturedEntry);
|
||||
List<LogEntry> capturedList = listCaptor.getValue();
|
||||
assertNotNull(capturedList);
|
||||
assertEquals(1, capturedList.size());
|
||||
|
||||
LogEntry capturedEntry = capturedList.get(0);
|
||||
Map<String, Object> attributes = capturedEntry.getAttributes();
|
||||
assertEquals("string_value", attributes.get("string_attr"));
|
||||
assertEquals(true, attributes.get("bool_attr"));
|
||||
@@ -145,12 +156,15 @@ class OtlpLogProtocolAdapterTest {
|
||||
|
||||
adapter.ingest(otlpPayload);
|
||||
|
||||
verify(commonDataQueue, times(1)).sendLogEntry(any(LogEntry.class));
|
||||
ArgumentCaptor<List<LogEntry>> listCaptor = ArgumentCaptor.forClass(List.class);
|
||||
verify(commonDataQueue, times(1)).sendLogEntryToStorageBatch(listCaptor.capture());
|
||||
verify(commonDataQueue, times(1)).sendLogEntryToAlertBatch(anyList());
|
||||
verify(logSseManager, times(1)).broadcast(any(LogEntry.class));
|
||||
|
||||
ArgumentCaptor<LogEntry> logEntryCaptor = ArgumentCaptor.forClass(LogEntry.class);
|
||||
verify(commonDataQueue).sendLogEntry(logEntryCaptor.capture());
|
||||
List<LogEntry> capturedList = listCaptor.getValue();
|
||||
assertEquals(1, capturedList.size());
|
||||
|
||||
LogEntry capturedEntry = logEntryCaptor.getValue();
|
||||
LogEntry capturedEntry = capturedList.get(0);
|
||||
assertEquals("1234567890abcdef1234567890abcdef", capturedEntry.getTraceId());
|
||||
assertEquals("1234567890abcdef", capturedEntry.getSpanId());
|
||||
assertEquals(1, capturedEntry.getTraceFlags());
|
||||
@@ -170,7 +184,15 @@ class OtlpLogProtocolAdapterTest {
|
||||
|
||||
adapter.ingest(otlpPayload);
|
||||
|
||||
verifyNoInteractions(commonDataQueue, logSseManager);
|
||||
ArgumentCaptor<List<LogEntry>> listCaptor = ArgumentCaptor.forClass(List.class);
|
||||
verify(commonDataQueue, times(1)).sendLogEntryToStorageBatch(listCaptor.capture());
|
||||
verify(commonDataQueue, times(1)).sendLogEntryToAlertBatch(anyList());
|
||||
|
||||
List<LogEntry> capturedList = listCaptor.getValue();
|
||||
assertNotNull(capturedList);
|
||||
assertEquals(0, capturedList.size());
|
||||
|
||||
verifyNoInteractions(logSseManager);
|
||||
}
|
||||
|
||||
private String createValidOtlpLogPayload() throws Exception {
|
||||
|
||||
+22
-8
@@ -18,13 +18,6 @@
|
||||
package org.apache.hertzbeat.manager.component.sd;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.arrow.RowWrapper;
|
||||
@@ -34,6 +27,9 @@ import org.apache.hertzbeat.common.entity.manager.MonitorBind;
|
||||
import org.apache.hertzbeat.common.entity.manager.Param;
|
||||
import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
import org.apache.hertzbeat.common.queue.CommonDataQueue;
|
||||
import org.apache.hertzbeat.common.support.exception.CommonDataQueueUnknownException;
|
||||
import org.apache.hertzbeat.common.util.BackoffUtils;
|
||||
import org.apache.hertzbeat.common.util.ExponentialBackoff;
|
||||
import org.apache.hertzbeat.manager.dao.CollectorMonitorBindDao;
|
||||
import org.apache.hertzbeat.manager.dao.MonitorBindDao;
|
||||
import org.apache.hertzbeat.manager.dao.MonitorDao;
|
||||
@@ -43,6 +39,14 @@ import org.apache.hertzbeat.manager.service.MonitorService;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Service Discovery Worker
|
||||
*/
|
||||
@@ -80,8 +84,13 @@ public class ServiceDiscoveryWorker implements InitializingBean {
|
||||
private class SdUpdateTask implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
ExponentialBackoff backoff = new ExponentialBackoff(50L, 1000L);
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
try (final CollectRep.MetricsData metricsData = dataQueue.pollServiceDiscoveryData()) {
|
||||
if (metricsData == null) {
|
||||
continue;
|
||||
}
|
||||
backoff.reset();
|
||||
Long monitorId = metricsData.getId();
|
||||
final Monitor mainMonitor = monitorDao.findById(monitorId).orElse(null);
|
||||
if (mainMonitor == null) {
|
||||
@@ -114,13 +123,14 @@ public class ServiceDiscoveryWorker implements InitializingBean {
|
||||
.filter(p -> !p.isEmpty())
|
||||
.orElse(defaultPort);
|
||||
final String keyStr = host + ":" + port;
|
||||
final String instance = port.isEmpty() ? host : host + ":" + port;
|
||||
if (subMonitorBindMap.containsKey(keyStr)) {
|
||||
subMonitorBindMap.remove(keyStr);
|
||||
continue;
|
||||
}
|
||||
Monitor newMonitor = mainMonitor.clone();
|
||||
newMonitor.setId(null);
|
||||
newMonitor.setHost(host);
|
||||
newMonitor.setInstance(instance);
|
||||
newMonitor.setName(newMonitor.getName() + "-" + host + ":" + port);
|
||||
newMonitor.setScrape(CommonConstants.SCRAPE_STATIC);
|
||||
newMonitor.setGmtCreate(LocalDateTime.now());
|
||||
@@ -153,6 +163,10 @@ public class ServiceDiscoveryWorker implements InitializingBean {
|
||||
final Set<Long> needCancelMonitorIdSet = subMonitorBindMap.values().stream()
|
||||
.map(MonitorBind::getMonitorId).collect(Collectors.toSet());
|
||||
monitorService.deleteMonitors(needCancelMonitorIdSet);
|
||||
} catch (CommonDataQueueUnknownException ue) {
|
||||
if (!BackoffUtils.shouldContinueAfterBackoff(backoff)) {
|
||||
break;
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
log.error(exception.getMessage(), exception);
|
||||
}
|
||||
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.manager.component.validator;
|
||||
|
||||
import org.apache.hertzbeat.common.entity.manager.Param;
|
||||
import org.apache.hertzbeat.common.entity.manager.ParamDefine;
|
||||
|
||||
/**
|
||||
* Parameter validator interface
|
||||
*/
|
||||
public interface ParamValidator {
|
||||
|
||||
/**
|
||||
* Check if the validator supports the given parameter type
|
||||
*
|
||||
* @param type parameter type
|
||||
* @return true if supported
|
||||
*/
|
||||
boolean support(String type);
|
||||
|
||||
/**
|
||||
* Validate the parameter
|
||||
*
|
||||
* @param paramDefine parameter definition
|
||||
* @param param parameter actual value
|
||||
* @throws IllegalArgumentException if validation fails
|
||||
*/
|
||||
void validate(ParamDefine paramDefine, Param param) throws IllegalArgumentException;
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.manager.component.validator;
|
||||
|
||||
import org.apache.hertzbeat.common.entity.manager.Param;
|
||||
import org.apache.hertzbeat.common.entity.manager.ParamDefine;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Parameter validator manager
|
||||
*/
|
||||
@Component
|
||||
public class ParamValidatorManager {
|
||||
|
||||
private final List<ParamValidator> validators;
|
||||
|
||||
public ParamValidatorManager(List<ParamValidator> validators) {
|
||||
this.validators = validators;
|
||||
}
|
||||
|
||||
public void validate(ParamDefine paramDefine, Param param) {
|
||||
for (ParamValidator validator : validators) {
|
||||
if (validator.support(paramDefine.getType())) {
|
||||
validator.validate(paramDefine, param);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// No validator found for the given type.
|
||||
throw new IllegalArgumentException("ParamDefine type " + paramDefine.getType() + " is invalid.");
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.manager.component.validator.impl;
|
||||
|
||||
import org.apache.hertzbeat.common.entity.manager.Param;
|
||||
import org.apache.hertzbeat.common.entity.manager.ParamDefine;
|
||||
import org.apache.hertzbeat.manager.component.validator.ParamValidator;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Array parameter validator
|
||||
*/
|
||||
@Component
|
||||
public class ArrayParamValidator implements ParamValidator {
|
||||
@Override
|
||||
public boolean support(String type) {
|
||||
return "array".equals(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(ParamDefine paramDefine, Param param) {
|
||||
String[] arrays = param.getParamValue().split(",");
|
||||
if (arrays.length == 0) {
|
||||
throw new IllegalArgumentException("Param field " + paramDefine.getField() + " value "
|
||||
+ param.getParamValue() + " is invalid arrays value");
|
||||
}
|
||||
if (param.getParamValue().startsWith("[") && param.getParamValue().endsWith("]")) {
|
||||
param.setParamValue(param.getParamValue().substring(1, param.getParamValue().length() - 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.manager.component.validator.impl;
|
||||
|
||||
import org.apache.hertzbeat.common.entity.manager.Param;
|
||||
import org.apache.hertzbeat.common.entity.manager.ParamDefine;
|
||||
import org.apache.hertzbeat.manager.component.validator.ParamValidator;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Boolean parameter validator
|
||||
*/
|
||||
@Component
|
||||
public class BooleanParamValidator implements ParamValidator {
|
||||
@Override
|
||||
public boolean support(String type) {
|
||||
return "boolean".equals(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(ParamDefine paramDefine, Param param) {
|
||||
String booleanValue = param.getParamValue();
|
||||
if (!"true".equalsIgnoreCase(booleanValue) && !"false".equalsIgnoreCase(booleanValue)) {
|
||||
throw new IllegalArgumentException("Params field " + paramDefine.getField() + " value "
|
||||
+ booleanValue + " is invalid boolean value.");
|
||||
}
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.manager.component.validator.impl;
|
||||
|
||||
import org.apache.hertzbeat.common.entity.manager.Param;
|
||||
import org.apache.hertzbeat.common.entity.manager.ParamDefine;
|
||||
import org.apache.hertzbeat.manager.component.validator.ParamValidator;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Host parameter validator that delegates to the common HostParamValidator
|
||||
*/
|
||||
@Component
|
||||
public class HostParamValidatorAdapter implements ParamValidator {
|
||||
|
||||
private final org.apache.hertzbeat.common.support.valid.HostParamValidator hostValidator;
|
||||
|
||||
public HostParamValidatorAdapter() {
|
||||
this.hostValidator = new org.apache.hertzbeat.common.support.valid.HostParamValidator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean support(String type) {
|
||||
return "host".equals(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(ParamDefine paramDefine, Param param) {
|
||||
if (!hostValidator.isValid(param.getParamValue(), null)) {
|
||||
throw new IllegalArgumentException("Params field " + paramDefine.getField() + " value "
|
||||
+ param.getParamValue() + " is invalid host value.");
|
||||
}
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.manager.component.validator.impl;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import org.apache.hertzbeat.common.entity.manager.Param;
|
||||
import org.apache.hertzbeat.common.entity.manager.ParamDefine;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.apache.hertzbeat.manager.component.validator.ParamValidator;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* JSON parameter validator
|
||||
*/
|
||||
@Component
|
||||
public class JsonParamValidator implements ParamValidator {
|
||||
@Override
|
||||
public boolean support(String type) {
|
||||
return "metrics-field".equals(type) || "key-value".equals(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(ParamDefine paramDefine, Param param) {
|
||||
if (JsonUtil.fromJson(param.getParamValue(), new TypeReference<>() {
|
||||
}) == null) {
|
||||
throw new IllegalArgumentException("Params field " + paramDefine.getField() + " value "
|
||||
+ param.getParamValue() + " is invalid key-value value");
|
||||
}
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.manager.component.validator.impl;
|
||||
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.manager.Param;
|
||||
import org.apache.hertzbeat.common.entity.manager.ParamDefine;
|
||||
import org.apache.hertzbeat.common.util.IntervalExpressionUtil;
|
||||
import org.apache.hertzbeat.manager.component.validator.ParamValidator;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Number parameter validator
|
||||
*/
|
||||
@Component
|
||||
public class NumberParamValidator implements ParamValidator {
|
||||
@Override
|
||||
public boolean support(String type) {
|
||||
return "number".equals(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(ParamDefine paramDefine, Param param) {
|
||||
Double doubleValue = org.apache.hertzbeat.common.util.CommonUtil.parseStrDouble(param.getParamValue());
|
||||
if (doubleValue == null) {
|
||||
throw new IllegalArgumentException("Params field " + paramDefine.getField() + " type "
|
||||
+ paramDefine.getType() + " is invalid.");
|
||||
}
|
||||
if (paramDefine.getRange() != null) {
|
||||
if (!IntervalExpressionUtil.validNumberIntervalExpress(doubleValue,
|
||||
paramDefine.getRange())) {
|
||||
throw new IllegalArgumentException("Params field " + paramDefine.getField() + " type "
|
||||
+ paramDefine.getType() + " over range " + paramDefine.getRange());
|
||||
}
|
||||
}
|
||||
param.setType(CommonConstants.PARAM_TYPE_NUMBER);
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.manager.component.validator.impl;
|
||||
|
||||
import org.apache.hertzbeat.common.entity.manager.Param;
|
||||
import org.apache.hertzbeat.common.entity.manager.ParamDefine;
|
||||
import org.apache.hertzbeat.manager.component.validator.ParamValidator;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Option parameter validator for radio and checkbox types
|
||||
*/
|
||||
@Component
|
||||
public class OptionParamValidator implements ParamValidator {
|
||||
@Override
|
||||
public boolean support(String type) {
|
||||
return "radio".equals(type) || "checkbox".equals(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(ParamDefine paramDefine, Param param) {
|
||||
List<ParamDefine.Option> options = paramDefine.getOptions();
|
||||
boolean invalid = true;
|
||||
if (options != null) {
|
||||
for (ParamDefine.Option option : options) {
|
||||
if (param.getParamValue().equalsIgnoreCase(option.getValue())) {
|
||||
invalid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (invalid) {
|
||||
throw new IllegalArgumentException("Params field " + paramDefine.getField() + " value "
|
||||
+ param.getParamValue() + " is invalid option value");
|
||||
}
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.manager.component.validator.impl;
|
||||
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.manager.Param;
|
||||
import org.apache.hertzbeat.common.entity.manager.ParamDefine;
|
||||
import org.apache.hertzbeat.common.util.AesUtil;
|
||||
import org.apache.hertzbeat.manager.component.validator.ParamValidator;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Password parameter validator
|
||||
*/
|
||||
@Component
|
||||
public class PasswordParamValidator implements ParamValidator {
|
||||
@Override
|
||||
public boolean support(String type) {
|
||||
return "password".equals(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(ParamDefine paramDefine, Param param) {
|
||||
String passwordValue = param.getParamValue();
|
||||
if (!AesUtil.isCiphertext(passwordValue)) {
|
||||
passwordValue = AesUtil.aesEncode(passwordValue);
|
||||
param.setParamValue(passwordValue);
|
||||
}
|
||||
param.setType(CommonConstants.PARAM_TYPE_PASSWORD);
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.manager.component.validator.impl;
|
||||
|
||||
import org.apache.hertzbeat.common.entity.manager.Param;
|
||||
import org.apache.hertzbeat.common.entity.manager.ParamDefine;
|
||||
import org.apache.hertzbeat.manager.component.validator.ParamValidator;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Text parameter validator
|
||||
*/
|
||||
@Component
|
||||
public class TextParamValidator implements ParamValidator {
|
||||
@Override
|
||||
public boolean support(String type) {
|
||||
return "text".equals(type) || "textarea".equals(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(ParamDefine paramDefine, Param param) {
|
||||
Short limit = paramDefine.getLimit();
|
||||
if (limit != null && param.getParamValue().length() > limit) {
|
||||
throw new IllegalArgumentException("Params field " + paramDefine.getField() + " type "
|
||||
+ paramDefine.getType() + " over limit " + limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -150,7 +150,7 @@ public class CollectorJobScheduler implements CollectorScheduling, CollectJobSch
|
||||
appDefine.setCyclic(true);
|
||||
appDefine.setTimestamp(System.currentTimeMillis());
|
||||
Map<String, String> metadata = Map.of(CommonConstants.LABEL_INSTANCE_NAME, monitor.getName(),
|
||||
CommonConstants.LABEL_INSTANCE_HOST, monitor.getHost());
|
||||
CommonConstants.LABEL_INSTANCE, monitor.getInstance());
|
||||
appDefine.setMetadata(metadata);
|
||||
List<Param> params = paramDao.findParamsByMonitorId(monitor.getId());
|
||||
List<Configmap> configmaps = params.stream()
|
||||
|
||||
+6
-2
@@ -50,7 +50,7 @@ import org.springframework.util.StringUtils;
|
||||
@Order(value = Ordered.LOWEST_PRECEDENCE - 1)
|
||||
@Slf4j
|
||||
public class SchedulerInit implements CommandLineRunner {
|
||||
|
||||
|
||||
@Autowired
|
||||
private CollectorScheduling collectorScheduling;
|
||||
|
||||
@@ -59,6 +59,7 @@ public class SchedulerInit implements CommandLineRunner {
|
||||
|
||||
private static final String MAIN_COLLECTOR_NODE_IP = "127.0.0.1";
|
||||
private static final String DEFAULT_COLLECTOR_VERSION = "DEBUG";
|
||||
public static final String PARAM_FIELD_PORT = "port";
|
||||
|
||||
@Autowired
|
||||
private AppService appService;
|
||||
@@ -117,8 +118,11 @@ public class SchedulerInit implements CommandLineRunner {
|
||||
appDefine.setDefaultInterval(monitor.getIntervals());
|
||||
appDefine.setCyclic(true);
|
||||
appDefine.setTimestamp(System.currentTimeMillis());
|
||||
|
||||
String instance = monitor.getInstance();
|
||||
|
||||
Map<String, String> metadata = Map.of(CommonConstants.LABEL_INSTANCE_NAME, monitor.getName(),
|
||||
CommonConstants.LABEL_INSTANCE_HOST, monitor.getHost());
|
||||
CommonConstants.LABEL_INSTANCE, instance);
|
||||
appDefine.setMetadata(metadata);
|
||||
appDefine.setLabels(monitor.getLabels());
|
||||
appDefine.setAnnotations(monitor.getAnnotations());
|
||||
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.manager.service.helper;
|
||||
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.common.constants.ExportFileConstants;
|
||||
import org.apache.hertzbeat.common.constants.SignConstants;
|
||||
import org.apache.hertzbeat.common.util.FileUtil;
|
||||
import org.apache.hertzbeat.manager.config.ManagerSseManager;
|
||||
import org.apache.hertzbeat.manager.service.ImExportService;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Helper class for monitor import and export operations
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class MonitorImExportHelper {
|
||||
|
||||
private static final String CONTENT_VALUE = MediaType.APPLICATION_OCTET_STREAM_VALUE + SignConstants.SINGLE_MARK
|
||||
+ "charset=" + StandardCharsets.UTF_8;
|
||||
|
||||
private final Map<String, ImExportService> imExportServiceMap = new HashMap<>();
|
||||
private final ManagerSseManager managerSseManager;
|
||||
|
||||
public MonitorImExportHelper(List<ImExportService> imExportServiceList, ManagerSseManager managerSseManager) {
|
||||
imExportServiceList.forEach(it -> imExportServiceMap.put(it.type(), it));
|
||||
this.managerSseManager = managerSseManager;
|
||||
}
|
||||
|
||||
public void export(List<Long> ids, String type, HttpServletResponse res) throws Exception {
|
||||
var imExportService = imExportServiceMap.get(type);
|
||||
if (imExportService == null) {
|
||||
throw new IllegalArgumentException("not support export type: " + type);
|
||||
}
|
||||
var fileName = imExportService.getFileName();
|
||||
res.setHeader(HttpHeaders.CONTENT_DISPOSITION, CONTENT_VALUE);
|
||||
res.setContentType(CONTENT_VALUE);
|
||||
res.setHeader(HttpHeaders.CONTENT_DISPOSITION,
|
||||
"attachment;filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8));
|
||||
res.setHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, HttpHeaders.CONTENT_DISPOSITION);
|
||||
imExportService.exportConfig(res.getOutputStream(), ids);
|
||||
}
|
||||
|
||||
public void importConfig(MultipartFile file) throws Exception {
|
||||
var fileName = FileUtil.getFileName(file);
|
||||
var type = FileUtil.getFileType(file);
|
||||
try {
|
||||
if (!imExportServiceMap.containsKey(type)) {
|
||||
String errMsg = ExportFileConstants.FILE + " " + fileName + " is not supported.";
|
||||
throw new RuntimeException(errMsg);
|
||||
}
|
||||
var imExportService = imExportServiceMap.get(type);
|
||||
imExportService.importConfig(fileName, file.getInputStream());
|
||||
} catch (Exception e) {
|
||||
managerSseManager.broadcastImportTaskFail(fileName, e.getMessage());
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-9
@@ -34,6 +34,7 @@ import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
import org.apache.hertzbeat.common.support.SpringContextHolder;
|
||||
import org.apache.hertzbeat.common.support.exception.CommonException;
|
||||
import org.apache.hertzbeat.common.util.CommonUtil;
|
||||
import org.apache.hertzbeat.common.util.HertzBeatKeywordsUtil;
|
||||
import org.apache.hertzbeat.common.util.JexlCheckerUtil;
|
||||
import org.apache.hertzbeat.manager.dao.DefineDao;
|
||||
import org.apache.hertzbeat.manager.dao.MonitorDao;
|
||||
@@ -439,24 +440,18 @@ public class AppServiceImpl implements AppService, InitializingBean {
|
||||
for (ParamDefine param : app.getParams()) {
|
||||
CommonUtil.validDefineI18n(param.getName(), param.getField() + " param");
|
||||
}
|
||||
for (Metrics metric : app.getMetrics()) {
|
||||
CommonUtil.validDefineI18n(metric.getI18n(), metric.getName() + " metric");
|
||||
if (metric.getFields() == null){
|
||||
continue;
|
||||
}
|
||||
for (Metrics.Field field : metric.getFields()) {
|
||||
CommonUtil.validDefineI18n(field.getI18n(), metric.getName() + " metric " + field.getField() + " field");
|
||||
}
|
||||
}
|
||||
if (!isModify) {
|
||||
Assert.isNull(appDefines.get(app.getApp().toLowerCase()),
|
||||
"monitoring template name " + app.getApp() + " already exists.");
|
||||
}
|
||||
Set<String> fieldsSet = new HashSet<>(16);
|
||||
for (Metrics metrics : app.getMetrics()) {
|
||||
CommonUtil.validDefineI18n(metrics.getI18n(), metrics.getName() + " metric");
|
||||
Assert.notEmpty(metrics.getFields(), "monitoring template metrics fields can not null");
|
||||
fieldsSet.clear();
|
||||
for (Metrics.Field field : metrics.getFields()) {
|
||||
CommonUtil.validDefineI18n(field.getI18n(), metrics.getName() + " metric " + field.getField() + " field");
|
||||
HertzBeatKeywordsUtil.verifyKeywords(field.getField());
|
||||
if (fieldsSet.contains(field.getField())) {
|
||||
throw new IllegalArgumentException(app.getApp() + " " + metrics.getName() + " "
|
||||
+ field.getField() + " can not duplicated.");
|
||||
|
||||
+2
-1
@@ -123,10 +123,11 @@ public class BulletinServiceImpl implements BulletinService {
|
||||
if (null == monitor) {
|
||||
continue;
|
||||
}
|
||||
|
||||
BulletinMetricsData.Data.DataBuilder dataBuilder = BulletinMetricsData.Data.builder()
|
||||
.monitorId(monitorId)
|
||||
.monitorName(monitor.getName())
|
||||
.host(monitor.getHost());
|
||||
.host(monitor.getInstance());
|
||||
|
||||
List<BulletinMetricsData.Metric> metrics = new ArrayList<>();
|
||||
Map<String, List<String>> fieldMap = bulletin.getFields();
|
||||
|
||||
+127
-220
@@ -17,7 +17,6 @@
|
||||
|
||||
package org.apache.hertzbeat.manager.service.impl;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.primitives.Longs;
|
||||
import com.usthe.sureness.subject.SubjectSum;
|
||||
@@ -29,8 +28,6 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.alert.dao.AlertDefineBindDao;
|
||||
import org.apache.hertzbeat.collector.dispatch.DispatchConstants;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.constants.ExportFileConstants;
|
||||
import org.apache.hertzbeat.common.constants.NetworkConstants;
|
||||
import org.apache.hertzbeat.common.constants.SignConstants;
|
||||
import org.apache.hertzbeat.common.entity.grafana.GrafanaDashboard;
|
||||
import org.apache.hertzbeat.common.entity.job.Configmap;
|
||||
@@ -45,15 +42,11 @@ import org.apache.hertzbeat.common.entity.manager.Param;
|
||||
import org.apache.hertzbeat.common.entity.manager.ParamDefine;
|
||||
import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
import org.apache.hertzbeat.common.support.event.MonitorDeletedEvent;
|
||||
import org.apache.hertzbeat.common.util.AesUtil;
|
||||
import org.apache.hertzbeat.common.util.FileUtil;
|
||||
import org.apache.hertzbeat.common.util.IntervalExpressionUtil;
|
||||
import org.apache.hertzbeat.common.util.IpDomainUtil;
|
||||
|
||||
import org.apache.hertzbeat.common.util.JexlCheckerUtil;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.apache.hertzbeat.common.util.SnowFlakeIdGenerator;
|
||||
import org.apache.hertzbeat.grafana.service.DashboardService;
|
||||
import org.apache.hertzbeat.manager.config.ManagerSseManager;
|
||||
import org.apache.hertzbeat.manager.component.validator.ParamValidatorManager;
|
||||
import org.apache.hertzbeat.manager.dao.CollectorDao;
|
||||
import org.apache.hertzbeat.manager.dao.CollectorMonitorBindDao;
|
||||
import org.apache.hertzbeat.manager.dao.LabelDao;
|
||||
@@ -65,10 +58,10 @@ import org.apache.hertzbeat.manager.pojo.dto.MetricsInfo;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.MonitorDto;
|
||||
import org.apache.hertzbeat.manager.scheduler.CollectJobScheduling;
|
||||
import org.apache.hertzbeat.manager.service.AppService;
|
||||
import org.apache.hertzbeat.manager.service.ImExportService;
|
||||
import org.apache.hertzbeat.manager.service.LabelService;
|
||||
import org.apache.hertzbeat.manager.service.MetricsFavoriteService;
|
||||
import org.apache.hertzbeat.manager.service.MonitorService;
|
||||
import org.apache.hertzbeat.manager.service.helper.MonitorImExportHelper;
|
||||
import org.apache.hertzbeat.manager.support.exception.MonitorDatabaseException;
|
||||
import org.apache.hertzbeat.manager.support.exception.MonitorDetectException;
|
||||
import org.apache.hertzbeat.warehouse.service.WarehouseService;
|
||||
@@ -79,16 +72,12 @@ import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
@@ -111,9 +100,12 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
public static final String PATTERN_HTTPS = "(?i)https://";
|
||||
private static final Long MONITOR_ID_TMP = 1000000000L;
|
||||
private static final byte ALL_MONITOR_STATUS = 9;
|
||||
public static final String PARAM_FIELD_PORT = "port";
|
||||
|
||||
private static final String CONTENT_VALUE = MediaType.APPLICATION_OCTET_STREAM_VALUE + SignConstants.SINGLE_MARK + "charset=" + StandardCharsets.UTF_8;
|
||||
private final Map<String, ImExportService> imExportServiceMap = new HashMap<>();
|
||||
@Autowired
|
||||
private ParamValidatorManager paramValidatorManager;
|
||||
@Autowired
|
||||
private MonitorImExportHelper monitorImExportHelper;
|
||||
@Autowired
|
||||
private AppService appService;
|
||||
@Autowired
|
||||
@@ -137,18 +129,12 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
@Autowired
|
||||
private DashboardService dashboardService;
|
||||
@Autowired
|
||||
private ManagerSseManager managerSseManager;
|
||||
@Autowired
|
||||
private LabelDao labelDao;
|
||||
@Autowired
|
||||
private LabelService labelService;
|
||||
@Autowired
|
||||
private MetricsFavoriteService metricsFavoriteService;
|
||||
|
||||
public MonitorServiceImpl(List<ImExportService> imExportServiceList) {
|
||||
imExportServiceList.forEach(it -> imExportServiceMap.put(it.type(), it));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public void detectMonitor(Monitor monitor, List<Param> params, String collector) throws MonitorDetectException {
|
||||
@@ -161,7 +147,8 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void addMonitor(Monitor monitor, List<Param> params, String collector, GrafanaDashboard grafanaDashboard) throws RuntimeException {
|
||||
public void addMonitor(Monitor monitor, List<Param> params, String collector, GrafanaDashboard grafanaDashboard)
|
||||
throws RuntimeException {
|
||||
// Apply for monitor id
|
||||
long monitorId = SnowFlakeIdGenerator.generateId();
|
||||
Map<String, String> labels = monitor.getLabels();
|
||||
@@ -176,12 +163,13 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
}
|
||||
|
||||
// Construct the collection task Job entity
|
||||
boolean isStatic = CommonConstants.SCRAPE_STATIC.equals(monitor.getScrape()) || !StringUtils.hasText(monitor.getScrape());
|
||||
boolean isStatic = CommonConstants.SCRAPE_STATIC.equals(monitor.getScrape())
|
||||
|| !StringUtils.hasText(monitor.getScrape());
|
||||
String app = isStatic ? monitor.getApp() : monitor.getScrape();
|
||||
Job appDefine = appService.getAppDefine(app);
|
||||
if (!isStatic) {
|
||||
appDefine.setSd(true);
|
||||
monitor.setHost("unknow");
|
||||
monitor.setInstance("unknow");
|
||||
}
|
||||
if (CommonConstants.PROMETHEUS.equals(monitor.getApp())) {
|
||||
appDefine.setApp(CommonConstants.PROMETHEUS_APP_PREFIX + monitor.getName());
|
||||
@@ -190,8 +178,23 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
appDefine.setDefaultInterval(monitor.getIntervals());
|
||||
appDefine.setCyclic(true);
|
||||
appDefine.setTimestamp(System.currentTimeMillis());
|
||||
|
||||
String instance = monitor.getInstance();
|
||||
// The port field may be null
|
||||
Param portParam = params.stream()
|
||||
.filter(param -> PARAM_FIELD_PORT.equals(param.getField()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
String portWithMark = (Objects.isNull(portParam) || !StringUtils.hasText(portParam.getParamValue()))
|
||||
? ""
|
||||
: SignConstants.DOUBLE_MARK + portParam.getParamValue();
|
||||
if (Objects.nonNull(instance)) {
|
||||
instance = instance + portWithMark;
|
||||
}
|
||||
monitor.setInstance(instance);
|
||||
|
||||
Map<String, String> metadata = Map.of(CommonConstants.LABEL_INSTANCE_NAME, monitor.getName(),
|
||||
CommonConstants.LABEL_INSTANCE_HOST, monitor.getHost());
|
||||
CommonConstants.LABEL_INSTANCE, instance);
|
||||
appDefine.setMetadata(metadata);
|
||||
appDefine.setLabels(monitor.getLabels());
|
||||
appDefine.setAnnotations(monitor.getAnnotations());
|
||||
@@ -200,11 +203,12 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
return new Configmap(param.getField(), param.getParamValue(), param.getType());
|
||||
}).collect(Collectors.toList());
|
||||
appDefine.setConfigmap(configmaps);
|
||||
long jobId = collector == null ? collectJobScheduling.addAsyncCollectJob(appDefine, null) :
|
||||
collectJobScheduling.addAsyncCollectJob(appDefine, collector);
|
||||
long jobId = collector == null ? collectJobScheduling.addAsyncCollectJob(appDefine, null)
|
||||
: collectJobScheduling.addAsyncCollectJob(appDefine, collector);
|
||||
try {
|
||||
detectMonitor(monitor, params, collector);
|
||||
} catch (Exception ignored) {}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
try {
|
||||
if (collector != null) {
|
||||
@@ -217,7 +221,8 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
monitor.setId(monitorId);
|
||||
monitor.setJobId(jobId);
|
||||
// create grafana dashboard
|
||||
if (monitor.getApp().equals(CommonConstants.PROMETHEUS) && grafanaDashboard != null && grafanaDashboard.isEnabled()) {
|
||||
if (monitor.getApp().equals(CommonConstants.PROMETHEUS) && grafanaDashboard != null
|
||||
&& grafanaDashboard.isEnabled()) {
|
||||
dashboardService.createOrUpdateDashboard(grafanaDashboard.getTemplate(), monitorId);
|
||||
}
|
||||
monitorDao.save(monitor);
|
||||
@@ -231,16 +236,7 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
|
||||
@Override
|
||||
public void export(List<Long> ids, String type, HttpServletResponse res) throws Exception {
|
||||
var imExportService = imExportServiceMap.get(type);
|
||||
if (imExportService == null) {
|
||||
throw new IllegalArgumentException("not support export type: " + type);
|
||||
}
|
||||
var fileName = imExportService.getFileName();
|
||||
res.setHeader(HttpHeaders.CONTENT_DISPOSITION, CONTENT_VALUE);
|
||||
res.setContentType(CONTENT_VALUE);
|
||||
res.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8));
|
||||
res.setHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, HttpHeaders.CONTENT_DISPOSITION);
|
||||
imExportService.exportConfig(res.getOutputStream(), ids);
|
||||
monitorImExportHelper.export(ids, type, res);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -250,35 +246,24 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
.stream()
|
||||
.map(Monitor::getId)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
|
||||
// Use the existing export method to export all monitors
|
||||
export(allMonitorIds, type, res);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void importConfig(MultipartFile file) throws Exception {
|
||||
var fileName = FileUtil.getFileName(file);
|
||||
var type = FileUtil.getFileType(file);
|
||||
try {
|
||||
if (!imExportServiceMap.containsKey(type)) {
|
||||
String errMsg = ExportFileConstants.FILE + " " + fileName + " is not supported.";
|
||||
throw new RuntimeException(errMsg);
|
||||
}
|
||||
var imExportService = imExportServiceMap.get(type);
|
||||
imExportService.importConfig(fileName, file.getInputStream());
|
||||
} catch (Exception e){
|
||||
managerSseManager.broadcastImportTaskFail(fileName, e.getMessage());
|
||||
throw e;
|
||||
}
|
||||
monitorImExportHelper.importConfig(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public void validate(MonitorDto monitorDto, Boolean isModify) throws IllegalArgumentException {
|
||||
// The request monitoring parameter matches the monitoring parameter definition mapping check
|
||||
// The request monitoring parameter matches the monitoring parameter definition
|
||||
// mapping check
|
||||
Monitor monitor = monitorDto.getMonitor();
|
||||
// The Service Discovery host field may be null
|
||||
monitor.setHost(StringUtils.hasText(monitor.getHost()) ? monitor.getHost().trim() : null);
|
||||
monitor.setInstance(StringUtils.hasText(monitor.getInstance()) ? monitor.getInstance().trim() : null);
|
||||
monitor.setName(monitor.getName().trim());
|
||||
Map<String, Param> paramMap = monitorDto.getParams()
|
||||
.stream()
|
||||
@@ -288,7 +273,7 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
param.setParamValue(value);
|
||||
})
|
||||
.collect(Collectors.toMap(Param::getField, param -> param));
|
||||
// Check name uniqueness and can not equal app type
|
||||
// Check name uniqueness and can not equal app type
|
||||
if (isModify != null) {
|
||||
Optional<Job> defineOptional = appService.getAppDefineOption(monitor.getName());
|
||||
if (defineOptional.isPresent()) {
|
||||
@@ -318,7 +303,8 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
// Parameter definition structure verification
|
||||
List<ParamDefine> paramDefines = appService.getAppParamDefines(monitorDto.getMonitor().getApp());
|
||||
if (!CollectionUtils.isEmpty(paramDefines)) {
|
||||
boolean isStatic = CommonConstants.SCRAPE_STATIC.equals(monitor.getScrape()) || !StringUtils.hasText(monitor.getScrape());
|
||||
boolean isStatic = CommonConstants.SCRAPE_STATIC.equals(monitor.getScrape())
|
||||
|| !StringUtils.hasText(monitor.getScrape());
|
||||
for (ParamDefine paramDefine : paramDefines) {
|
||||
String field = paramDefine.getField();
|
||||
Param param = paramMap.get(field);
|
||||
@@ -330,123 +316,7 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
throw new IllegalArgumentException("Params field " + field + " is required.");
|
||||
}
|
||||
if (param != null && StringUtils.hasText(param.getParamValue())) {
|
||||
switch (paramDefine.getType()) {
|
||||
case "number":
|
||||
double doubleValue;
|
||||
try {
|
||||
doubleValue = Double.parseDouble(param.getParamValue());
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("Params field " + field + " type "
|
||||
+ paramDefine.getType() + " is invalid.");
|
||||
}
|
||||
if (paramDefine.getRange() != null) {
|
||||
if (!IntervalExpressionUtil.validNumberIntervalExpress(doubleValue,
|
||||
paramDefine.getRange())) {
|
||||
throw new IllegalArgumentException("Params field " + field + " type "
|
||||
+ paramDefine.getType() + " over range " + paramDefine.getRange());
|
||||
}
|
||||
}
|
||||
param.setType(CommonConstants.PARAM_TYPE_NUMBER);
|
||||
break;
|
||||
case "textarea":
|
||||
Short textareaLimit = paramDefine.getLimit();
|
||||
if (textareaLimit != null && param.getParamValue().length() > textareaLimit) {
|
||||
throw new IllegalArgumentException("Params field " + field + " type "
|
||||
+ paramDefine.getType() + " over limit " + param.getParamValue());
|
||||
}
|
||||
break;
|
||||
case "text":
|
||||
Short textLimit = paramDefine.getLimit();
|
||||
if (textLimit != null && param.getParamValue().length() > textLimit) {
|
||||
throw new IllegalArgumentException("Params field " + field + " type "
|
||||
+ paramDefine.getType() + " over limit " + textLimit);
|
||||
}
|
||||
break;
|
||||
case "host":
|
||||
String hostValue = param.getParamValue();
|
||||
if (hostValue.toLowerCase().contains(NetworkConstants.HTTP_HEADER)) {
|
||||
hostValue = hostValue.replaceAll(PATTERN_HTTP, SignConstants.BLANK);
|
||||
}
|
||||
if (hostValue.toLowerCase().contains(NetworkConstants.HTTPS_HEADER)) {
|
||||
hostValue = hostValue.replace(PATTERN_HTTPS, SignConstants.BLANK);
|
||||
}
|
||||
if (!IpDomainUtil.validateIpDomain(hostValue)) {
|
||||
throw new IllegalArgumentException("Params field " + field + " value "
|
||||
+ hostValue + " is invalid host value.");
|
||||
}
|
||||
break;
|
||||
case "password":
|
||||
// The plaintext password needs to be encrypted for transmission and storage
|
||||
String passwordValue = param.getParamValue();
|
||||
if (!AesUtil.isCiphertext(passwordValue)) {
|
||||
passwordValue = AesUtil.aesEncode(passwordValue);
|
||||
param.setParamValue(passwordValue);
|
||||
}
|
||||
param.setType(CommonConstants.PARAM_TYPE_PASSWORD);
|
||||
break;
|
||||
case "boolean":
|
||||
// boolean check
|
||||
String booleanValue = param.getParamValue();
|
||||
if (!"true".equalsIgnoreCase(booleanValue) && !"false".equalsIgnoreCase(booleanValue)) {
|
||||
throw new IllegalArgumentException("Params field " + field + " value "
|
||||
+ booleanValue + " is invalid boolean value.");
|
||||
}
|
||||
break;
|
||||
case "radio":
|
||||
// radio single value check
|
||||
List<ParamDefine.Option> options = paramDefine.getOptions();
|
||||
boolean invalid = true;
|
||||
if (options != null) {
|
||||
for (ParamDefine.Option option : options) {
|
||||
if (param.getParamValue().equalsIgnoreCase(option.getValue())) {
|
||||
invalid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (invalid) {
|
||||
throw new IllegalArgumentException("Params field " + field + " value "
|
||||
+ param.getParamValue() + " is invalid option value");
|
||||
}
|
||||
break;
|
||||
case "checkbox":
|
||||
List<ParamDefine.Option> checkboxOptions = paramDefine.getOptions();
|
||||
boolean checkboxInvalid = true;
|
||||
if (checkboxOptions != null) {
|
||||
for (ParamDefine.Option option : checkboxOptions) {
|
||||
if (param.getParamValue().equalsIgnoreCase(option.getValue())) {
|
||||
checkboxInvalid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (checkboxInvalid) {
|
||||
throw new IllegalArgumentException("Params field " + field + " value "
|
||||
+ param.getParamValue() + " is invalid checkbox value");
|
||||
}
|
||||
break;
|
||||
case "metrics-field":
|
||||
case "key-value":
|
||||
if (JsonUtil.fromJson(param.getParamValue(), new TypeReference<>() {
|
||||
}) == null) {
|
||||
throw new IllegalArgumentException("Params field " + field + " value "
|
||||
+ param.getParamValue() + " is invalid key-value value");
|
||||
}
|
||||
break;
|
||||
case "array":
|
||||
String[] arrays = param.getParamValue().split(",");
|
||||
if (arrays.length == 0) {
|
||||
throw new IllegalArgumentException("Param field" + field + " value "
|
||||
+ param.getParamValue() + " is invalid arrays value");
|
||||
}
|
||||
if (param.getParamValue().startsWith("[") && param.getParamValue().endsWith("]")) {
|
||||
param.setParamValue(param.getParamValue().substring(1, param.getParamValue().length() - 1));
|
||||
}
|
||||
break;
|
||||
// todo More parameter definitions and actual value format verification
|
||||
default:
|
||||
throw new IllegalArgumentException("ParamDefine type " + paramDefine.getType() + " is invalid.");
|
||||
}
|
||||
paramValidatorManager.validate(paramDefine, param);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -470,7 +340,8 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
}
|
||||
if (JexlCheckerUtil.verifyStartCharacter(field.getField())) {
|
||||
throw new IllegalArgumentException(job.getApp() + " " + metrics.getName() + " "
|
||||
+ field.getField() + " illegal start character, please modify the template information.");
|
||||
+ field.getField()
|
||||
+ " illegal start character, please modify the template information.");
|
||||
}
|
||||
if (JexlCheckerUtil.verifySpaces(field.getField())) {
|
||||
throw new IllegalArgumentException(job.getApp() + " " + metrics.getName() + " "
|
||||
@@ -483,7 +354,8 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void modifyMonitor(Monitor monitor, List<Param> params, String collector, GrafanaDashboard grafanaDashboard) throws RuntimeException {
|
||||
public void modifyMonitor(Monitor monitor, List<Param> params, String collector, GrafanaDashboard grafanaDashboard)
|
||||
throws RuntimeException {
|
||||
long monitorId = monitor.getId();
|
||||
// Check to determine whether the monitor corresponding to the monitor id exists
|
||||
Optional<Monitor> queryOption = monitorDao.findById(monitorId);
|
||||
@@ -507,7 +379,22 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
labelDao.saveAll(addLabels);
|
||||
}
|
||||
|
||||
boolean isStatic = CommonConstants.SCRAPE_STATIC.equals(monitor.getScrape()) || !StringUtils.hasText(monitor.getScrape());
|
||||
String instance = monitor.getInstance();
|
||||
// The port field may be null
|
||||
Param portParam = params.stream()
|
||||
.filter(param -> PARAM_FIELD_PORT.equals(param.getField()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
String portWithMark = (Objects.isNull(portParam) || !StringUtils.hasText(portParam.getParamValue()))
|
||||
? ""
|
||||
: SignConstants.DOUBLE_MARK + portParam.getParamValue();
|
||||
if (Objects.nonNull(instance)) {
|
||||
instance = instance + portWithMark;
|
||||
}
|
||||
monitor.setInstance(instance);
|
||||
|
||||
boolean isStatic = CommonConstants.SCRAPE_STATIC.equals(monitor.getScrape())
|
||||
|| !StringUtils.hasText(monitor.getScrape());
|
||||
if (preMonitor.getStatus() != CommonConstants.MONITOR_PAUSED_CODE) {
|
||||
// Construct the collection task Job entity
|
||||
String app = isStatic ? monitor.getApp() : monitor.getScrape();
|
||||
@@ -526,12 +413,13 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
appDefine.setScheduleType(monitor.getScheduleType());
|
||||
appDefine.setCronExpression(monitor.getCronExpression());
|
||||
Map<String, String> metadata = Map.of(CommonConstants.LABEL_INSTANCE_NAME, monitor.getName(),
|
||||
CommonConstants.LABEL_INSTANCE_HOST, monitor.getHost());
|
||||
CommonConstants.LABEL_INSTANCE, monitor.getInstance());
|
||||
appDefine.setMetadata(metadata);
|
||||
appDefine.setLabels(monitor.getLabels());
|
||||
appDefine.setAnnotations(monitor.getAnnotations());
|
||||
List<Configmap> configmaps = params.stream().map(param ->
|
||||
new Configmap(param.getField(), param.getParamValue(), param.getType())).collect(Collectors.toList());
|
||||
List<Configmap> configmaps = params.stream()
|
||||
.map(param -> new Configmap(param.getField(), param.getParamValue(), param.getType()))
|
||||
.collect(Collectors.toList());
|
||||
appDefine.setConfigmap(configmaps);
|
||||
long newJobId;
|
||||
if (collector == null) {
|
||||
@@ -544,7 +432,8 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
// execute only in non paused status
|
||||
try {
|
||||
detectMonitor(monitor, params, collector);
|
||||
} catch (Exception ignored) {}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
// After the update is successfully released, refresh the database
|
||||
@@ -556,7 +445,8 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
.build();
|
||||
collectorMonitorBindDao.save(collectorMonitorBind);
|
||||
}
|
||||
// force update gmtUpdate time, due the case: monitor not change, param change. we also think monitor change
|
||||
// force update gmtUpdate time, due the case: monitor not change, param change.
|
||||
// we also think monitor change
|
||||
monitor.setGmtUpdate(LocalDateTime.now());
|
||||
// update or open grafana dashboard
|
||||
if (monitor.getApp().equals(CommonConstants.PROMETHEUS) && grafanaDashboard != null) {
|
||||
@@ -588,7 +478,8 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
if (CollectionUtils.isEmpty(ids)) {
|
||||
return;
|
||||
}
|
||||
Set<Long> subMonitorIds = monitorBindDao.findMonitorBindsByBizIdIn(ids).stream().map(MonitorBind::getMonitorId).collect(Collectors.toSet());
|
||||
Set<Long> subMonitorIds = monitorBindDao.findMonitorBindsByBizIdIn(ids).stream().map(MonitorBind::getMonitorId)
|
||||
.collect(Collectors.toSet());
|
||||
Set<Long> allMonitorIds = new HashSet<>(ids);
|
||||
allMonitorIds.addAll(subMonitorIds);
|
||||
List<Monitor> monitors = monitorDao.findMonitorsByIdIn(allMonitorIds);
|
||||
@@ -628,24 +519,29 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
List<Param> params = paramDao.findParamsByMonitorId(id);
|
||||
monitorDto.setParams(params);
|
||||
List<MetricsInfo> metricsInfos;
|
||||
if (DispatchConstants.PROTOCOL_PROMETHEUS.equalsIgnoreCase(monitor.getApp()) || monitor.getType() == CommonConstants.MONITOR_TYPE_PUSH_AUTO_CREATE) {
|
||||
if (DispatchConstants.PROTOCOL_PROMETHEUS.equalsIgnoreCase(monitor.getApp())
|
||||
|| monitor.getType() == CommonConstants.MONITOR_TYPE_PUSH_AUTO_CREATE) {
|
||||
List<CollectRep.MetricsData> metricsDataList = warehouseService.queryMonitorMetricsData(id);
|
||||
metricsInfos = metricsDataList.stream()
|
||||
.map(t -> MetricsInfo.builder().name(t.getMetrics()).favorited(favoritedMetrics.contains(t.getMetrics())).build())
|
||||
.map(t -> MetricsInfo.builder().name(t.getMetrics())
|
||||
.favorited(favoritedMetrics.contains(t.getMetrics())).build())
|
||||
.collect(Collectors.toList());
|
||||
monitorDto.setGrafanaDashboard(dashboardService.getDashboardByMonitorId(id));
|
||||
} else {
|
||||
boolean isStatic = CommonConstants.SCRAPE_STATIC.equals(monitor.getScrape()) || !StringUtils.hasText(monitor.getScrape());
|
||||
boolean isStatic = CommonConstants.SCRAPE_STATIC.equals(monitor.getScrape())
|
||||
|| !StringUtils.hasText(monitor.getScrape());
|
||||
String type = isStatic ? monitor.getApp() : monitor.getScrape();
|
||||
Job job = appService.getAppDefine(type);
|
||||
metricsInfos = job.getMetrics().stream()
|
||||
.filter(Metrics::isVisible)
|
||||
.map(t -> MetricsInfo.builder().name(t.getName()).favorited(favoritedMetrics.contains(t.getName())).build())
|
||||
.map(t -> MetricsInfo.builder().name(t.getName())
|
||||
.favorited(favoritedMetrics.contains(t.getName())).build())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
monitorDto.setMetrics(metricsInfos);
|
||||
monitorDto.setMonitor(monitor);
|
||||
Optional<CollectorMonitorBind> bindOptional = collectorMonitorBindDao.findCollectorMonitorBindByMonitorId(monitor.getId());
|
||||
Optional<CollectorMonitorBind> bindOptional = collectorMonitorBindDao
|
||||
.findCollectorMonitorBindByMonitorId(monitor.getId());
|
||||
bindOptional.ifPresent(bind -> monitorDto.setCollector(bind.getCollector()));
|
||||
return monitorDto;
|
||||
} else {
|
||||
@@ -654,7 +550,8 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<Monitor> getMonitors(List<Long> monitorIds, String app, String search, Byte status, String sort, String order, int pageIndex, int pageSize, String labels) {
|
||||
public Page<Monitor> getMonitors(List<Long> monitorIds, String app, String search, Byte status, String sort,
|
||||
String order, int pageIndex, int pageSize, String labels) {
|
||||
Specification<Monitor> specification = (root, query, criteriaBuilder) -> {
|
||||
List<Predicate> andList = new ArrayList<>();
|
||||
if (!CollectionUtils.isEmpty(monitorIds)) {
|
||||
@@ -678,7 +575,8 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
List<Predicate> orList = new ArrayList<>();
|
||||
if (StringUtils.hasText(search)) {
|
||||
Predicate predicateHost = criteriaBuilder.like(root.get("host"), "%" + search + "%");
|
||||
Predicate predicateName = criteriaBuilder.like(criteriaBuilder.lower(root.get("name")), "%" + search.toLowerCase() + "%");
|
||||
Predicate predicateName = criteriaBuilder.like(criteriaBuilder.lower(root.get("name")),
|
||||
"%" + search.toLowerCase() + "%");
|
||||
Long id = Longs.tryParse(search);
|
||||
if (id != null) {
|
||||
orList.add(criteriaBuilder.equal(root.get("id"), id));
|
||||
@@ -726,12 +624,13 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
return;
|
||||
}
|
||||
// Update monitoring status Delete corresponding monitoring periodic task
|
||||
// The jobId is not deleted, and the jobId is reused again after the management is started.
|
||||
Set<Long> subMonitorIds = monitorBindDao.findMonitorBindsByBizIdIn(ids).stream().map(MonitorBind::getMonitorId).collect(Collectors.toSet());
|
||||
// The jobId is not deleted, and the jobId is reused again after the management
|
||||
// is started.
|
||||
Set<Long> subMonitorIds = monitorBindDao.findMonitorBindsByBizIdIn(ids).stream().map(MonitorBind::getMonitorId)
|
||||
.collect(Collectors.toSet());
|
||||
ids.addAll(subMonitorIds);
|
||||
List<Monitor> managedMonitors = monitorDao.findMonitorsByIdIn(ids)
|
||||
.stream().filter(monitor ->
|
||||
monitor.getStatus() != CommonConstants.MONITOR_PAUSED_CODE)
|
||||
.stream().filter(monitor -> monitor.getStatus() != CommonConstants.MONITOR_PAUSED_CODE)
|
||||
.peek(monitor -> monitor.setStatus(CommonConstants.MONITOR_PAUSED_CODE))
|
||||
.collect(Collectors.toList());
|
||||
if (!CollectionUtils.isEmpty(managedMonitors)) {
|
||||
@@ -745,11 +644,11 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
@Override
|
||||
public void enableManageMonitors(Set<Long> ids) {
|
||||
// Update monitoring status Add corresponding monitoring periodic task
|
||||
Set<Long> subMonitorIds = monitorBindDao.findMonitorBindsByBizIdIn(ids).stream().map(MonitorBind::getMonitorId).collect(Collectors.toSet());
|
||||
Set<Long> subMonitorIds = monitorBindDao.findMonitorBindsByBizIdIn(ids).stream().map(MonitorBind::getMonitorId)
|
||||
.collect(Collectors.toSet());
|
||||
ids.addAll(subMonitorIds);
|
||||
List<Monitor> unManagedMonitors = monitorDao.findMonitorsByIdIn(ids)
|
||||
.stream().filter(monitor ->
|
||||
monitor.getStatus() == CommonConstants.MONITOR_PAUSED_CODE)
|
||||
.stream().filter(monitor -> monitor.getStatus() == CommonConstants.MONITOR_PAUSED_CODE)
|
||||
.peek(monitor -> monitor.setStatus(CommonConstants.MONITOR_UP_CODE))
|
||||
.collect(Collectors.toList());
|
||||
if (unManagedMonitors.isEmpty()) {
|
||||
@@ -759,7 +658,8 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
for (Monitor monitor : unManagedMonitors) {
|
||||
// Construct the collection task Job entity
|
||||
List<Param> params = paramDao.findParamsByMonitorId(monitor.getId());
|
||||
boolean isStatic = CommonConstants.SCRAPE_STATIC.equals(monitor.getScrape()) || !StringUtils.hasText(monitor.getScrape());
|
||||
boolean isStatic = CommonConstants.SCRAPE_STATIC.equals(monitor.getScrape())
|
||||
|| !StringUtils.hasText(monitor.getScrape());
|
||||
String app = isStatic ? monitor.getApp() : monitor.getScrape();
|
||||
Job appDefine = appService.getAppDefine(app);
|
||||
if (!isStatic) {
|
||||
@@ -775,26 +675,28 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
appDefine.setScheduleType(monitor.getScheduleType());
|
||||
appDefine.setCronExpression(monitor.getCronExpression());
|
||||
Map<String, String> metadata = Map.of(CommonConstants.LABEL_INSTANCE_NAME, monitor.getName(),
|
||||
CommonConstants.LABEL_INSTANCE_HOST, monitor.getHost());
|
||||
CommonConstants.LABEL_INSTANCE, monitor.getInstance());
|
||||
appDefine.setMetadata(metadata);
|
||||
appDefine.setLabels(monitor.getLabels());
|
||||
appDefine.setAnnotations(monitor.getAnnotations());
|
||||
List<Configmap> configmaps = params.stream().map(param ->
|
||||
new Configmap(param.getField(), param.getParamValue(), param.getType())).collect(Collectors.toList());
|
||||
List<Configmap> configmaps = params.stream()
|
||||
.map(param -> new Configmap(param.getField(), param.getParamValue(), param.getType()))
|
||||
.collect(Collectors.toList());
|
||||
List<ParamDefine> paramDefaultValue = appDefine.getParams().stream()
|
||||
.filter(item -> StringUtils.hasText(item.getDefaultValue()))
|
||||
.toList();
|
||||
paramDefaultValue.forEach(defaultVar -> {
|
||||
if (configmaps.stream().noneMatch(item -> item.getKey().equals(defaultVar.getField()))) {
|
||||
Configmap configmap = new Configmap(defaultVar.getField(), defaultVar.getDefaultValue(), CommonConstants.TYPE_STRING);
|
||||
Configmap configmap = new Configmap(defaultVar.getField(), defaultVar.getDefaultValue(),
|
||||
CommonConstants.TYPE_STRING);
|
||||
configmaps.add(configmap);
|
||||
}
|
||||
});
|
||||
appDefine.setConfigmap(configmaps);
|
||||
|
||||
// Issue collection tasks
|
||||
Optional<CollectorMonitorBind> bindOptional =
|
||||
collectorMonitorBindDao.findCollectorMonitorBindByMonitorId(monitor.getId());
|
||||
Optional<CollectorMonitorBind> bindOptional = collectorMonitorBindDao
|
||||
.findCollectorMonitorBindByMonitorId(monitor.getId());
|
||||
String collector = bindOptional.map(CollectorMonitorBind::getCollector).orElse(null);
|
||||
long newJobId = collectJobScheduling.addAsyncCollectJob(appDefine, collector);
|
||||
monitor.setJobId(newJobId);
|
||||
@@ -813,24 +715,26 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
if (CollectionUtils.isEmpty(appCounts)) {
|
||||
return null;
|
||||
}
|
||||
//Statistical category information, calculate the number of corresponding states for each monitor
|
||||
// Statistical category information, calculate the number of corresponding
|
||||
// states for each monitor
|
||||
Map<String, AppCount> appCountMap = new HashMap<>(appCounts.size());
|
||||
for (AppCount item : appCounts) {
|
||||
AppCount appCount = appCountMap.getOrDefault(item.getApp(), new AppCount());
|
||||
appCount.setApp(item.getApp());
|
||||
switch (item.getStatus()) {
|
||||
case CommonConstants.MONITOR_UP_CODE ->
|
||||
appCount.setAvailableSize(appCount.getAvailableSize() + item.getSize());
|
||||
appCount.setAvailableSize(appCount.getAvailableSize() + item.getSize());
|
||||
case CommonConstants.MONITOR_DOWN_CODE ->
|
||||
appCount.setUnAvailableSize(appCount.getUnAvailableSize() + item.getSize());
|
||||
appCount.setUnAvailableSize(appCount.getUnAvailableSize() + item.getSize());
|
||||
case CommonConstants.MONITOR_PAUSED_CODE ->
|
||||
appCount.setUnManageSize(appCount.getUnManageSize() + item.getSize());
|
||||
appCount.setUnManageSize(appCount.getUnManageSize() + item.getSize());
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
appCountMap.put(item.getApp(), appCount);
|
||||
}
|
||||
//Traverse the map obtained by statistics and convert it into a List<App Count> result set
|
||||
// Traverse the map obtained by statistics and convert it into a List<App Count>
|
||||
// result set
|
||||
return appCountMap.values().stream().map(item -> {
|
||||
item.setSize(item.getAvailableSize() + item.getUnManageSize() + item.getUnAvailableSize());
|
||||
try {
|
||||
@@ -871,7 +775,7 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
appDefine.setCyclic(true);
|
||||
appDefine.setTimestamp(System.currentTimeMillis());
|
||||
Map<String, String> metadata = Map.of(CommonConstants.LABEL_INSTANCE_NAME, monitor.getName(),
|
||||
CommonConstants.LABEL_INSTANCE_HOST, monitor.getHost());
|
||||
CommonConstants.LABEL_INSTANCE, monitor.getInstance());
|
||||
appDefine.setMetadata(metadata);
|
||||
appDefine.setLabels(monitor.getLabels());
|
||||
appDefine.setAnnotations(monitor.getAnnotations());
|
||||
@@ -883,7 +787,8 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
.toList();
|
||||
paramDefaultValue.forEach(defaultVar -> {
|
||||
if (configmaps.stream().noneMatch(item -> item.getKey().equals(defaultVar.getField()))) {
|
||||
Configmap configmap = new Configmap(defaultVar.getField(), defaultVar.getDefaultValue(), (byte) 1);
|
||||
Configmap configmap = new Configmap(defaultVar.getField(), defaultVar.getDefaultValue(),
|
||||
(byte) 1);
|
||||
configmaps.add(configmap);
|
||||
}
|
||||
});
|
||||
@@ -899,7 +804,7 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Monitor getMonitor(Long monitorId) {
|
||||
return monitorDao.findById(monitorId).orElse(null);
|
||||
@@ -950,7 +855,6 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
addMonitor(newMonitor, newParams, null, null);
|
||||
}
|
||||
|
||||
|
||||
private void detectSdMonitor(Monitor monitor, List<Param> params, String collector) {
|
||||
Long monitorId = monitor.getId();
|
||||
if (monitorId == null || monitorId == 0) {
|
||||
@@ -967,8 +871,9 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
appDefine.setMetadata(metadata);
|
||||
appDefine.setLabels(monitor.getLabels());
|
||||
appDefine.setAnnotations(monitor.getAnnotations());
|
||||
List<Configmap> configmaps = params.stream().map(param ->
|
||||
new Configmap(param.getField(), param.getParamValue(), param.getType())).collect(Collectors.toList());
|
||||
List<Configmap> configmaps = params.stream()
|
||||
.map(param -> new Configmap(param.getField(), param.getParamValue(), param.getType()))
|
||||
.collect(Collectors.toList());
|
||||
appDefine.setConfigmap(configmaps);
|
||||
appDefine.setSd(true);
|
||||
List<CollectRep.MetricsData> collectRep;
|
||||
@@ -1003,14 +908,16 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
appDefine.setCyclic(false);
|
||||
appDefine.setTimestamp(System.currentTimeMillis());
|
||||
Map<String, String> metadata = Map.of(CommonConstants.LABEL_INSTANCE_NAME, monitor.getName(),
|
||||
CommonConstants.LABEL_INSTANCE_HOST, monitor.getHost());
|
||||
CommonConstants.LABEL_INSTANCE, monitor.getInstance());
|
||||
appDefine.setMetadata(metadata);
|
||||
appDefine.setLabels(monitor.getLabels());
|
||||
appDefine.setAnnotations(monitor.getAnnotations());
|
||||
List<Configmap> configmaps = params.stream().map(param ->
|
||||
new Configmap(param.getField(), param.getParamValue(), param.getType())).collect(Collectors.toList());
|
||||
List<Configmap> configmaps = params.stream()
|
||||
.map(param -> new Configmap(param.getField(), param.getParamValue(), param.getType()))
|
||||
.collect(Collectors.toList());
|
||||
appDefine.setConfigmap(configmaps);
|
||||
// To detect availability, you only need to collect the set of availability metrics with a priority of 0.
|
||||
// To detect availability, you only need to collect the set of availability
|
||||
// metrics with a priority of 0.
|
||||
List<Metrics> availableMetrics = appDefine.getMetrics().stream()
|
||||
.filter(item -> item.getPriority() == 0).collect(Collectors.toList());
|
||||
appDefine.setMetrics(availableMetrics);
|
||||
|
||||
@@ -0,0 +1,381 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# The monitoring type category:service-application service monitoring db-database monitoring custom-custom monitoring os-operating system monitoring
|
||||
category: db
|
||||
# The monitoring type eg: linux windows tomcat mysql aws...
|
||||
app: db2
|
||||
# The monitoring i18n name
|
||||
name:
|
||||
zh-CN: DB2数据库
|
||||
en-US: DB2 DB
|
||||
ja-JP: DB2 データベース
|
||||
# The description and help of this monitoring type
|
||||
help:
|
||||
zh-CN: HertzBeat 使用 <a class='help_module_content' href='https://hertzbeat.apache.org/docs/advanced/extend-jdbc'> JDBC 协议</a> 通过配置 SQL 对 DB2 数据库的通用性能指标(表空间、性能状态、用户连接、缓存、慢SQL等)进行采集监控。<br>您可以点击“<i>新建 DB2 数据库</i>”并进行配置,或者选择“<i>更多操作</i>”,导入已有配置。
|
||||
en-US: HertzBeat uses <a class='help_module_content' href='https://hertzbeat.apache.org/docs/advanced/extend-jdbc'> JDBC Protocol</a> to configure SQL for collecting general metrics of DB2 database (tablespace、user connect、performance、slow SQL etc.). <br>You can click "<i>New DB2 Database</i>" and configure it, or select "<i>More Operations"</i> to import the existing configuration.
|
||||
zh-TW: HertzBeat 使用 <a class='help_module_content' href='https://hertzbeat.apache.org/docs/advanced/extend-jdbc'> JDBC 協議</a> 通過配置 SQL 對 DB2 數據庫的通用性能指標(表空間、性能狀態、用戶連接、緩存、慢SQL等)進行采集監控。<br>您可以點擊“<i>新建 DB2 數據庫</i>”並進行配置,或者選擇“<i>更多操作</i>”,導入已有配置。
|
||||
ja-JP: Hertzbeat は <a class='help_module_content' href='https://hertzbeat.apache.org/docs/advanced/extend-jdbc'> JDBCプロトコルを介して</a> DB2 データベースの一般的なパフォーマンスのメトリクスを監視します。<br>「<i>新規 DB2 データベース</i>」をクリックしてパラメタを設定した後、新規することができます。
|
||||
helpLink:
|
||||
zh-CN: https://hertzbeat.apache.org/zh-cn/docs/help/db2/
|
||||
en-US: https://hertzbeat.apache.org/docs/help/db2/
|
||||
# Input params define for monitoring(render web ui by the definition)
|
||||
params:
|
||||
# field-param field key
|
||||
- field: host
|
||||
# name-param field display i18n name
|
||||
name:
|
||||
zh-CN: 目标Host
|
||||
en-US: Target Host
|
||||
ja-JP: 目標ホスト
|
||||
# type-param field type(most mapping the html input type)
|
||||
type: host
|
||||
# required-true or false
|
||||
required: true
|
||||
# field-param field key
|
||||
- field: port
|
||||
# name-param field display i18n name
|
||||
name:
|
||||
zh-CN: 端口
|
||||
en-US: Port
|
||||
ja-JP: ポート
|
||||
# type-param field type(most mapping the html input type)
|
||||
type: number
|
||||
# when type is number, range is required
|
||||
range: '[0,65535]'
|
||||
# required-true or false
|
||||
required: true
|
||||
# default value
|
||||
defaultValue: 50000 # DB2 default port
|
||||
- field: timeout
|
||||
name:
|
||||
zh-CN: 查询超时时间(ms)
|
||||
en-US: Query Timeout(ms)
|
||||
ja-JP: クエリタイムアウト(ms)
|
||||
type: number
|
||||
range: '[400,200000]'
|
||||
required: false
|
||||
hide: true
|
||||
defaultValue: 6000
|
||||
- field: reuseConnection
|
||||
name:
|
||||
zh-CN: 复用连接
|
||||
en-US: Reuse Connection
|
||||
ja-JP: 接続再利用
|
||||
type: boolean
|
||||
required: false
|
||||
defaultValue: true
|
||||
- field: database
|
||||
name:
|
||||
zh-CN: 数据库名
|
||||
en-US: Database Name
|
||||
ja-JP: データベース名
|
||||
type: text
|
||||
required: true # Database Name is typically mandatory for DB2 JDBC connection
|
||||
- field: username
|
||||
name:
|
||||
zh-CN: 用户名
|
||||
en-US: Username
|
||||
ja-JP: ユーザー名
|
||||
type: text
|
||||
limit: 50
|
||||
required: false
|
||||
- field: password
|
||||
name:
|
||||
zh-CN: 密码
|
||||
en-US: Password
|
||||
ja-JP: パスワード
|
||||
type: password
|
||||
required: false
|
||||
- field: url
|
||||
name:
|
||||
zh-CN: URL
|
||||
en-US: URL
|
||||
ja-JP: URL
|
||||
type: text
|
||||
required: false
|
||||
hide: true
|
||||
|
||||
# collect metrics config list
|
||||
metrics:
|
||||
# metrics - basic
|
||||
- name: basic
|
||||
i18n:
|
||||
zh-CN: 基本信息
|
||||
en-US: Basic Info
|
||||
ja-JP: 基礎情報
|
||||
priority: 0
|
||||
fields:
|
||||
- field: database_version
|
||||
type: 1
|
||||
label: true
|
||||
i18n:
|
||||
zh-CN: 数据库版本
|
||||
en-US: Database Version
|
||||
ja-JP: データベースバージョン
|
||||
- field: instance_name
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 实例名
|
||||
en-US: Instance Name
|
||||
ja-JP: インスタンス名
|
||||
- field: status
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 实例状态
|
||||
en-US: Instance Status
|
||||
ja-JP: インスタンス的状态
|
||||
- field: num_db
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: 数据库数量
|
||||
en-US: Number of Databases
|
||||
ja-JP: データベースの数
|
||||
aliasFields:
|
||||
- INSTANCE_NAME
|
||||
- INSTANCE_STATUS
|
||||
- NUM_DB
|
||||
- CURRENT_USER
|
||||
calculates:
|
||||
- database_version=INSTANCE_NAME
|
||||
- instance_name=INSTANCE_NAME
|
||||
- status=INSTANCE_STATUS
|
||||
- num_db=NUM_DB
|
||||
protocol: jdbc
|
||||
jdbc:
|
||||
host: ^_^host^_^
|
||||
port: ^_^port^_^
|
||||
platform: db2
|
||||
username: ^_^username^_^
|
||||
password: ^_^password^_^
|
||||
database: ^_^database^_^
|
||||
timeout: ^_^timeout^_^
|
||||
reuseConnection: ^_^reuseConnection^_^
|
||||
queryType: oneRow
|
||||
sql: "SELECT CURRENT SERVER AS INSTANCE_NAME, 'ACTIVE' AS INSTANCE_STATUS, 1 AS NUM_DB, CURRENT USER AS CURRENT_USER FROM SYSIBM.SYSDUMMY1"
|
||||
url: ^_^url^_^
|
||||
|
||||
# Combined tablespace capacity and usage
|
||||
- name: tablespace_usage
|
||||
i18n:
|
||||
zh-CN: 表空间使用率
|
||||
en-US: Tablespace Usage
|
||||
ja-JP: 表領域使用率
|
||||
priority: 1
|
||||
fields:
|
||||
- field: tablespace_name
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 表空间名
|
||||
en-US: Tablespace Name
|
||||
ja-JP: 表領域名
|
||||
label: true
|
||||
- field: status
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 类型
|
||||
en-US: Type
|
||||
ja-JP: タイプ
|
||||
- field: total
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: 总容量
|
||||
en-US: Total Size
|
||||
ja-JP: 総容量
|
||||
unit: MB
|
||||
- field: used
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: 已使用
|
||||
en-US: Used Size
|
||||
ja-JP: 使用済み
|
||||
unit: MB
|
||||
- field: free
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: 空闲
|
||||
en-US: Free Size
|
||||
ja-JP: 利用可能
|
||||
unit: MB
|
||||
- field: used_percentage
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: 已用百分比
|
||||
en-US: Used Percentage
|
||||
ja-JP: 使用した比率
|
||||
unit: '%'
|
||||
aliasFields:
|
||||
- TBSP_NAME
|
||||
- TBSP_TYPE
|
||||
- TBSP_TOTAL_SIZE_KB
|
||||
- TBSP_USED_SIZE_KB
|
||||
- TBSP_FREE_SIZE_KB
|
||||
calculates:
|
||||
- tablespace_name=TBSP_NAME
|
||||
- status=TBSP_TYPE
|
||||
- total=TBSP_TOTAL_SIZE_KB / 1024
|
||||
- used=TBSP_USED_SIZE_KB / 1024
|
||||
- free=TBSP_FREE_SIZE_KB / 1024
|
||||
- used_percentage=TBSP_USED_SIZE_KB / TBSP_TOTAL_SIZE_KB * 100
|
||||
protocol: jdbc
|
||||
jdbc:
|
||||
host: ^_^host^_^
|
||||
port: ^_^port^_^
|
||||
platform: db2
|
||||
username: ^_^username^_^
|
||||
password: ^_^password^_^
|
||||
database: ^_^database^_^
|
||||
timeout: ^_^timeout^_^
|
||||
reuseConnection: ^_^reuseConnection^_^
|
||||
queryType: multiRow
|
||||
sql: "SELECT TBSP_NAME, TBSP_TYPE, TBSP_TOTAL_SIZE_KB, TBSP_USED_SIZE_KB, TBSP_FREE_SIZE_KB FROM SYSIBMADM.TBSP_UTILIZATION"
|
||||
url: ^_^url^_^
|
||||
|
||||
# Sessions/Applications status distribution
|
||||
- name: sessions_status
|
||||
i18n:
|
||||
zh-CN: 会话状态
|
||||
en-US: Session Status
|
||||
ja-JP: セッション状態
|
||||
priority: 2
|
||||
fields:
|
||||
- field: status_type
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 状态类型
|
||||
en-US: Status Type
|
||||
ja-JP: 状態タイプ
|
||||
label: true
|
||||
- field: count
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: 数量
|
||||
en-US: Count
|
||||
ja-JP: 数
|
||||
protocol: jdbc
|
||||
jdbc:
|
||||
host: ^_^host^_^
|
||||
port: ^_^port^_^
|
||||
platform: db2
|
||||
username: ^_^username^_^
|
||||
password: ^_^password^_^
|
||||
database: ^_^database^_^
|
||||
timeout: ^_^timeout^_^
|
||||
reuseConnection: ^_^reuseConnection^_^
|
||||
queryType: multiRow
|
||||
sql: "SELECT APPL_STATUS AS STATUS_TYPE, COUNT(*) AS COUNT FROM SYSIBMADM.APPLICATIONS GROUP BY APPL_STATUS"
|
||||
url: ^_^url^_^
|
||||
|
||||
|
||||
# Total number of applications/sessions
|
||||
- name: process
|
||||
i18n:
|
||||
zh-CN: 应用进程数
|
||||
en-US: Application Process Count
|
||||
ja-JP: アプリケーションプロセス数
|
||||
priority: 5
|
||||
fields:
|
||||
- field: process_count
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: 进程数
|
||||
en-US: Process Count
|
||||
ja-JP: プロセス数
|
||||
protocol: jdbc
|
||||
jdbc:
|
||||
host: ^_^host^_^
|
||||
port: ^_^port^_^
|
||||
platform: db2
|
||||
username: ^_^username^_^
|
||||
password: ^_^password^_^
|
||||
database: ^_^database^_^
|
||||
timeout: ^_^timeout^_^
|
||||
reuseConnection: ^_^reuseConnection^_^
|
||||
queryType: oneRow
|
||||
# Count of all running applications
|
||||
sql: "select count(*) as process_count from SYSIBMADM.APPLICATIONS"
|
||||
url: ^_^url^_^
|
||||
|
||||
- name: locks
|
||||
i18n:
|
||||
zh-CN: 锁
|
||||
en-US: Locks
|
||||
ja-JP: ロック
|
||||
priority: 10
|
||||
fields:
|
||||
- field: waiting_locks
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: 当前等待锁数
|
||||
en-US: Current Waiting Locks
|
||||
ja-JP: 現在の待機中ロック数
|
||||
aliasFields:
|
||||
- WAITING_LOCKS
|
||||
calculates:
|
||||
- waiting_locks=WAITING_LOCKS
|
||||
protocol: jdbc
|
||||
jdbc:
|
||||
host: ^_^host^_^
|
||||
port: ^_^port^_^
|
||||
platform: db2
|
||||
username: ^_^username^_^
|
||||
password: ^_^password^_^
|
||||
database: ^_^database^_^
|
||||
timeout: ^_^timeout^_^
|
||||
reuseConnection: ^_^reuseConnection^_^
|
||||
queryType: oneRow
|
||||
sql: "SELECT COUNT(*) AS WAITING_LOCKS FROM SYSIBMADM.MON_LOCKWAITS"
|
||||
url: ^_^url^_^
|
||||
|
||||
# Slow Query / Top Statements (by Total CPU Time)
|
||||
- name: slow_query
|
||||
i18n:
|
||||
zh-CN: 慢查询
|
||||
en-US: Slow Query
|
||||
ja-JP: スロークエリ
|
||||
priority: 14
|
||||
fields:
|
||||
- field: avg_exe_time
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: 平均执行时间
|
||||
en-US: Avg Execution Time
|
||||
ja-JP: 平均実行時間
|
||||
unit: ms
|
||||
- field: sql_text
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: SQL文本
|
||||
en-US: SQL Text
|
||||
ja-JP: SQLテキスト
|
||||
aliasFields:
|
||||
- AVG_EXE_TIME
|
||||
- SQL_TEXT
|
||||
calculates:
|
||||
- avg_exe_time=AVG_EXE_TIME
|
||||
- sql_text=SQL_TEXT
|
||||
protocol: jdbc
|
||||
jdbc:
|
||||
host: ^_^host^_^
|
||||
port: ^_^port^_^
|
||||
platform: db2
|
||||
username: ^_^username^_^
|
||||
password: ^_^password^_^
|
||||
database: ^_^database^_^
|
||||
timeout: ^_^timeout^_^
|
||||
reuseConnection: ^_^reuseConnection^_^
|
||||
queryType: multiRow
|
||||
sql: "select total_act_time/num_exec_with_metrics as AVG_EXE_TIME, substr(stmt_text,1,4000) as SQL_TEXT from table(mon_get_pkg_cache_stmt(null, null, null, -2)) as T where num_exec_with_metrics>=1 order by 1 desc fetch first 100 rows only"
|
||||
url: ^_^url^_^
|
||||
@@ -162,7 +162,7 @@ metrics:
|
||||
# metrics fields list.
|
||||
fields:
|
||||
# Metric information includes the following: Field name, Type: 0-number, 1-string, instance: indicates whether the metric is the primary key, unit: the unit of the metric
|
||||
- field: instance
|
||||
- field: metric_instance
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 实例
|
||||
@@ -180,6 +180,12 @@ metrics:
|
||||
zh-CN: 值
|
||||
en-US: Value
|
||||
ja-JP: 値
|
||||
aliasFields:
|
||||
- instance
|
||||
- timestamp
|
||||
- value
|
||||
calculates:
|
||||
- metric_instance=instance
|
||||
# Monitoring protocol used for data collection, e.g. sql, ssh, http, telnet, wmi, snmp, sdk.
|
||||
protocol: http
|
||||
# When the protocol is HTTP, the specific collection configuration is as follows
|
||||
@@ -201,7 +207,7 @@ metrics:
|
||||
^_^headers^_^: ^_^headers^_^
|
||||
# Request parameter content
|
||||
params:
|
||||
^_^params^_^: ^_^params^_^
|
||||
query: basic_influxdb_memstats_alloc
|
||||
# authorization
|
||||
authorization:
|
||||
# Authentication method: Basic Auth, Digest Auth, Bearer Token
|
||||
@@ -231,7 +237,7 @@ metrics:
|
||||
zh-CN: 任务
|
||||
en-US: Job
|
||||
ja-JP: タスク
|
||||
- field: instance
|
||||
- field: metric_instance
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 实例
|
||||
@@ -255,6 +261,14 @@ metrics:
|
||||
zh-CN: 值
|
||||
en-US: Value
|
||||
ja-JP: 値
|
||||
aliasFields:
|
||||
- job
|
||||
- instance
|
||||
- database
|
||||
- timestamp
|
||||
- value
|
||||
calculates:
|
||||
- metric_instance=instance
|
||||
# Monitoring protocol used for data collection, e.g. sql, ssh, http, telnet, wmi, snmp, sdk.
|
||||
protocol: http
|
||||
# When the protocol is HTTP, the specific collection configuration is as follows
|
||||
@@ -300,7 +314,7 @@ metrics:
|
||||
# metrics fields list.
|
||||
fields:
|
||||
# Metric information includes the following: Field name, Type: 0-number, 1-string, instance: indicates whether the metric is the primary key, unit: the unit of the metric
|
||||
- field: instance
|
||||
- field: metric_instance
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 实例
|
||||
@@ -318,6 +332,12 @@ metrics:
|
||||
zh-CN: 值
|
||||
en-US: Value
|
||||
ja-JP: 値
|
||||
aliasFields:
|
||||
- instance
|
||||
- timestamp
|
||||
- value
|
||||
calculates:
|
||||
- metric_instance=instance
|
||||
# Monitoring protocol used for data collection, e.g. sql, ssh, http, telnet, wmi, snmp, sdk.
|
||||
protocol: http
|
||||
# When the protocol is HTTP, the specific collection configuration is as follows
|
||||
@@ -364,7 +384,7 @@ metrics:
|
||||
# metrics fields list.
|
||||
fields:
|
||||
# Metric information includes the following: Field name, Type: 0-number, 1-string, instance: indicates whether the metric is the primary key, unit: the unit of the metric
|
||||
- field: instance
|
||||
- field: metric_instance
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 实例
|
||||
@@ -382,6 +402,12 @@ metrics:
|
||||
zh-CN: 值
|
||||
en-US: Value
|
||||
ja-JP: 値
|
||||
aliasFields:
|
||||
- instance
|
||||
- timestamp
|
||||
- value
|
||||
calculates:
|
||||
- metric_instance=instance
|
||||
# Monitoring protocol used for data collection, e.g. sql, ssh, http, telnet, wmi, snmp, sdk.
|
||||
protocol: http
|
||||
# When the protocol is HTTP, the specific collection configuration is as follows
|
||||
|
||||
@@ -166,7 +166,7 @@ metrics:
|
||||
zh-CN: 名称
|
||||
en-US: Name
|
||||
ja-JP: 名前
|
||||
- field: instance
|
||||
- field: metric_instance
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 实例
|
||||
@@ -184,6 +184,13 @@ metrics:
|
||||
zh-CN: 数值
|
||||
en-US: Value
|
||||
ja-JP: 値
|
||||
aliasFields:
|
||||
- __name__
|
||||
- instance
|
||||
- timestamp
|
||||
- value
|
||||
calculates:
|
||||
- metric_instance=instance
|
||||
# The protocol used for monitoring, eg: sql, ssh, http, telnet, wmi, snmp, sdk
|
||||
protocol: http
|
||||
# The config content when protocol is http
|
||||
@@ -295,7 +302,7 @@ metrics:
|
||||
# collect metrics content
|
||||
fields:
|
||||
# field-metric name, type-metric type(0-number,1-string), unit-metric unit('%','ms','MB'), label-whether it is a metrics label field
|
||||
- field: instance
|
||||
- field: metric_instance
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 实例
|
||||
@@ -325,6 +332,14 @@ metrics:
|
||||
zh-CN: 数值
|
||||
en-US: Value
|
||||
ja-JP: 値
|
||||
aliasFields:
|
||||
- instance
|
||||
- job
|
||||
- topic
|
||||
- timestamp
|
||||
- value
|
||||
calculates:
|
||||
- metric_instance=instance
|
||||
# The protocol used for monitoring, eg: sql, ssh, http, telnet, wmi, snmp, sdk
|
||||
protocol: http
|
||||
# The specific collection configuration when the protocol is http
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
## 🔔 HertzBeat Alert Notification
|
||||
|
||||
### Alert Summary
|
||||
> - Status: ${status}
|
||||
<#if commonLabels??>
|
||||
<#if commonLabels.severity??>
|
||||
> - Severity: ${commonLabels.severity?switch("critical", "❤️ Critical", "warning", "💛 Warning", "info", "💚 Info", "Unknown")}
|
||||
</#if>
|
||||
<#if commonLabels.alertname??>
|
||||
> - Alert Name: ${commonLabels.alertname}
|
||||
</#if>
|
||||
</#if>
|
||||
|
||||
### Alert Details
|
||||
<#list alerts as alert>
|
||||
#### Alert ${alert?index + 1}
|
||||
<#if alert.labels??>
|
||||
<#list alert.labels?keys as key>
|
||||
> - ${key}: ${alert.labels[key]}
|
||||
</#list>
|
||||
</#if>
|
||||
<#if alert.content?? && alert.content != "">
|
||||
> - Content: ${alert.content}
|
||||
</#if>
|
||||
> - Trigger Count: ${alert.triggerTimes}
|
||||
> - Start Time: ${(alert.startAt?number_to_datetime)?string('yyyy-MM-dd HH:mm:ss')}
|
||||
<#if alert.activeAt??>
|
||||
> - Active Time: ${(alert.activeAt?number_to_datetime)?string('yyyy-MM-dd HH:mm:ss')}
|
||||
</#if>
|
||||
<#if alert.endAt??>
|
||||
> - End Time: ${(alert.endAt?number_to_datetime)?string('yyyy-MM-dd HH:mm:ss')}
|
||||
</#if>
|
||||
|
||||
<#if alert.annotations?? && alert.annotations?size gt 0>
|
||||
##### Additional Information
|
||||
<#list alert.annotations?keys as key>
|
||||
> - ${key}: ${alert.annotations[key]}
|
||||
</#list>
|
||||
</#if>
|
||||
</#list>
|
||||
|
||||
<#if commonAnnotations?? && commonAnnotations?size gt 0>
|
||||
### Common Information
|
||||
<#list commonAnnotations?keys as key>
|
||||
> - ${key}: ${commonAnnotations[key]}
|
||||
</#list>
|
||||
</#if>
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.manager.component.validator;
|
||||
|
||||
import org.apache.hertzbeat.common.entity.manager.Param;
|
||||
import org.apache.hertzbeat.common.entity.manager.ParamDefine;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class ParamValidatorManagerTest {
|
||||
|
||||
private ParamValidatorManager paramValidatorManager;
|
||||
|
||||
@Mock
|
||||
private ParamValidator paramValidator;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
paramValidatorManager = new ParamValidatorManager(List.of(paramValidator));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_Success() {
|
||||
ParamDefine paramDefine = new ParamDefine();
|
||||
paramDefine.setType("text");
|
||||
Param param = new Param();
|
||||
|
||||
when(paramValidator.support("text")).thenReturn(true);
|
||||
|
||||
assertDoesNotThrow(() -> paramValidatorManager.validate(paramDefine, param));
|
||||
verify(paramValidator).validate(paramDefine, param);
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_NoValidatorFound() {
|
||||
ParamDefine paramDefine = new ParamDefine();
|
||||
paramDefine.setType("unknown");
|
||||
Param param = new Param();
|
||||
|
||||
when(paramValidator.support("unknown")).thenReturn(false);
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> paramValidatorManager.validate(paramDefine, param));
|
||||
}
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.manager.component.validator.impl;
|
||||
|
||||
import org.apache.hertzbeat.common.entity.manager.Param;
|
||||
import org.apache.hertzbeat.common.entity.manager.ParamDefine;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class ArrayParamValidatorTest {
|
||||
|
||||
private ArrayParamValidator validator;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
validator = new ArrayParamValidator();
|
||||
}
|
||||
|
||||
@Test
|
||||
void support() {
|
||||
assertTrue(validator.support("array"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_ValidArray() {
|
||||
ParamDefine paramDefine = new ParamDefine();
|
||||
paramDefine.setType("array");
|
||||
Param param = new Param();
|
||||
param.setParamValue("val1,val2");
|
||||
|
||||
assertDoesNotThrow(() -> validator.validate(paramDefine, param));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_ValidArrayWithBrackets() {
|
||||
ParamDefine paramDefine = new ParamDefine();
|
||||
paramDefine.setType("array");
|
||||
Param param = new Param();
|
||||
param.setParamValue("[val1,val2]");
|
||||
|
||||
validator.validate(paramDefine, param);
|
||||
assertEquals("val1,val2", param.getParamValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_EmptyArray() {
|
||||
ParamDefine paramDefine = new ParamDefine();
|
||||
paramDefine.setType("array");
|
||||
paramDefine.setField("tags");
|
||||
Param param = new Param();
|
||||
param.setParamValue("");
|
||||
assertDoesNotThrow(() -> validator.validate(paramDefine, param));
|
||||
assertEquals("", param.getParamValue());
|
||||
}
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.manager.component.validator.impl;
|
||||
|
||||
import org.apache.hertzbeat.common.entity.manager.Param;
|
||||
import org.apache.hertzbeat.common.entity.manager.ParamDefine;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class BooleanParamValidatorTest {
|
||||
|
||||
private BooleanParamValidator validator;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
validator = new BooleanParamValidator();
|
||||
}
|
||||
|
||||
@Test
|
||||
void support() {
|
||||
assertTrue(validator.support("boolean"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_True() {
|
||||
ParamDefine paramDefine = new ParamDefine();
|
||||
paramDefine.setType("boolean");
|
||||
Param param = new Param();
|
||||
param.setParamValue("true");
|
||||
|
||||
assertDoesNotThrow(() -> validator.validate(paramDefine, param));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_False() {
|
||||
ParamDefine paramDefine = new ParamDefine();
|
||||
paramDefine.setType("boolean");
|
||||
Param param = new Param();
|
||||
param.setParamValue("false");
|
||||
|
||||
assertDoesNotThrow(() -> validator.validate(paramDefine, param));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_CaseInsensitive() {
|
||||
ParamDefine paramDefine = new ParamDefine();
|
||||
paramDefine.setType("boolean");
|
||||
Param param = new Param();
|
||||
param.setParamValue("True");
|
||||
|
||||
assertDoesNotThrow(() -> validator.validate(paramDefine, param));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_Invalid() {
|
||||
ParamDefine paramDefine = new ParamDefine();
|
||||
paramDefine.setType("boolean");
|
||||
paramDefine.setField("ssl");
|
||||
Param param = new Param();
|
||||
param.setParamValue("yes");
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> validator.validate(paramDefine, param));
|
||||
}
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.manager.component.validator.impl;
|
||||
|
||||
import org.apache.hertzbeat.common.entity.manager.Param;
|
||||
import org.apache.hertzbeat.common.entity.manager.ParamDefine;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class HostParamValidatorAdapterTest {
|
||||
|
||||
private HostParamValidatorAdapter validator;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
validator = new HostParamValidatorAdapter();
|
||||
}
|
||||
|
||||
@Test
|
||||
void support() {
|
||||
assertTrue(validator.support("host"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_ValidHost() {
|
||||
ParamDefine paramDefine = new ParamDefine();
|
||||
paramDefine.setType("host");
|
||||
Param param = new Param();
|
||||
param.setParamValue("127.0.0.1");
|
||||
|
||||
assertDoesNotThrow(() -> validator.validate(paramDefine, param));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_ValidDomain() {
|
||||
ParamDefine paramDefine = new ParamDefine();
|
||||
paramDefine.setType("host");
|
||||
Param param = new Param();
|
||||
param.setParamValue("localhost");
|
||||
|
||||
assertDoesNotThrow(() -> validator.validate(paramDefine, param));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_HttpPrefix() {
|
||||
ParamDefine paramDefine = new ParamDefine();
|
||||
paramDefine.setType("host");
|
||||
Param param = new Param();
|
||||
param.setParamValue("http://127.0.0.1");
|
||||
|
||||
assertDoesNotThrow(() -> validator.validate(paramDefine, param));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_InvalidHost() {
|
||||
ParamDefine paramDefine = new ParamDefine();
|
||||
paramDefine.setType("host");
|
||||
paramDefine.setField("host");
|
||||
Param param = new Param();
|
||||
param.setParamValue("invalid host");
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> validator.validate(paramDefine, param));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_HostWithPort() {
|
||||
ParamDefine paramDefine = new ParamDefine();
|
||||
paramDefine.setType("host");
|
||||
Param param = new Param();
|
||||
param.setParamValue("127.0.0.1:8080");
|
||||
|
||||
assertDoesNotThrow(() -> validator.validate(paramDefine, param));
|
||||
}
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.manager.component.validator.impl;
|
||||
|
||||
import org.apache.hertzbeat.common.entity.manager.Param;
|
||||
import org.apache.hertzbeat.common.entity.manager.ParamDefine;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class JsonParamValidatorTest {
|
||||
|
||||
private JsonParamValidator validator;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
validator = new JsonParamValidator();
|
||||
}
|
||||
|
||||
@Test
|
||||
void support() {
|
||||
assertTrue(validator.support("metrics-field"));
|
||||
assertTrue(validator.support("key-value"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_ValidJson() {
|
||||
ParamDefine paramDefine = new ParamDefine();
|
||||
paramDefine.setType("key-value");
|
||||
Param param = new Param();
|
||||
param.setParamValue("{\"key\":\"value\"}");
|
||||
|
||||
assertDoesNotThrow(() -> validator.validate(paramDefine, param));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_InvalidJson() {
|
||||
ParamDefine paramDefine = new ParamDefine();
|
||||
paramDefine.setType("key-value");
|
||||
paramDefine.setField("headers");
|
||||
Param param = new Param();
|
||||
param.setParamValue("{key:value}"); // Invalid JSON
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> validator.validate(paramDefine, param));
|
||||
}
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.manager.component.validator.impl;
|
||||
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.manager.Param;
|
||||
import org.apache.hertzbeat.common.entity.manager.ParamDefine;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class NumberParamValidatorTest {
|
||||
|
||||
private NumberParamValidator validator;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
validator = new NumberParamValidator();
|
||||
}
|
||||
|
||||
@Test
|
||||
void support() {
|
||||
assertTrue(validator.support("number"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_ValidNumber() {
|
||||
ParamDefine paramDefine = new ParamDefine();
|
||||
paramDefine.setType("number");
|
||||
Param param = new Param();
|
||||
param.setParamValue("123");
|
||||
|
||||
validator.validate(paramDefine, param);
|
||||
assertEquals(CommonConstants.PARAM_TYPE_NUMBER, param.getType());
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_InvalidNumber() {
|
||||
ParamDefine paramDefine = new ParamDefine();
|
||||
paramDefine.setType("number");
|
||||
paramDefine.setField("port");
|
||||
Param param = new Param();
|
||||
param.setParamValue("abc");
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> validator.validate(paramDefine, param));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_NumberInRange() {
|
||||
ParamDefine paramDefine = new ParamDefine();
|
||||
paramDefine.setType("number");
|
||||
paramDefine.setRange("[0,100]");
|
||||
Param param = new Param();
|
||||
param.setParamValue("50");
|
||||
|
||||
validator.validate(paramDefine, param);
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_NumberOutOfRange() {
|
||||
ParamDefine paramDefine = new ParamDefine();
|
||||
paramDefine.setType("number");
|
||||
paramDefine.setField("port");
|
||||
paramDefine.setRange("[0,100]");
|
||||
Param param = new Param();
|
||||
param.setParamValue("150");
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> validator.validate(paramDefine, param));
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.manager.component.validator.impl;
|
||||
|
||||
import org.apache.hertzbeat.common.entity.manager.Param;
|
||||
import org.apache.hertzbeat.common.entity.manager.ParamDefine;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class OptionParamValidatorTest {
|
||||
|
||||
private OptionParamValidator validator;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
validator = new OptionParamValidator();
|
||||
}
|
||||
|
||||
@Test
|
||||
void support() {
|
||||
assertTrue(validator.support("radio"));
|
||||
assertTrue(validator.support("checkbox"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_ValidOption() {
|
||||
ParamDefine paramDefine = new ParamDefine();
|
||||
paramDefine.setType("radio");
|
||||
paramDefine.setOptions(List.of(new ParamDefine.Option("opt1", "val1"), new ParamDefine.Option("opt2", "val2")));
|
||||
Param param = new Param();
|
||||
param.setParamValue("val1");
|
||||
|
||||
assertDoesNotThrow(() -> validator.validate(paramDefine, param));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_InvalidOption() {
|
||||
ParamDefine paramDefine = new ParamDefine();
|
||||
paramDefine.setType("radio");
|
||||
paramDefine.setField("method");
|
||||
paramDefine.setOptions(List.of(new ParamDefine.Option("opt1", "val1")));
|
||||
Param param = new Param();
|
||||
param.setParamValue("val2");
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> validator.validate(paramDefine, param));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_NullOptions() {
|
||||
ParamDefine paramDefine = new ParamDefine();
|
||||
paramDefine.setType("radio");
|
||||
paramDefine.setField("method");
|
||||
Param param = new Param();
|
||||
param.setParamValue("val1");
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> validator.validate(paramDefine, param));
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.manager.component.validator.impl;
|
||||
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.manager.Param;
|
||||
import org.apache.hertzbeat.common.entity.manager.ParamDefine;
|
||||
import org.apache.hertzbeat.common.util.AesUtil;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class PasswordParamValidatorTest {
|
||||
|
||||
private PasswordParamValidator validator;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
validator = new PasswordParamValidator();
|
||||
}
|
||||
|
||||
@Test
|
||||
void support() {
|
||||
assertTrue(validator.support("password"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_EncryptsPlaintext() {
|
||||
ParamDefine paramDefine = new ParamDefine();
|
||||
paramDefine.setType("password");
|
||||
Param param = new Param();
|
||||
String plaintext = "password123";
|
||||
param.setParamValue(plaintext);
|
||||
|
||||
validator.validate(paramDefine, param);
|
||||
|
||||
assertNotEquals(plaintext, param.getParamValue());
|
||||
assertTrue(AesUtil.isCiphertext(param.getParamValue()));
|
||||
assertEquals(CommonConstants.PARAM_TYPE_PASSWORD, param.getType());
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_IgnoresCiphertext() {
|
||||
ParamDefine paramDefine = new ParamDefine();
|
||||
paramDefine.setType("password");
|
||||
Param param = new Param();
|
||||
String ciphertext = AesUtil.aesEncode("password123");
|
||||
param.setParamValue(ciphertext);
|
||||
|
||||
validator.validate(paramDefine, param);
|
||||
|
||||
assertEquals(ciphertext, param.getParamValue());
|
||||
assertEquals(CommonConstants.PARAM_TYPE_PASSWORD, param.getType());
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.manager.component.validator.impl;
|
||||
|
||||
import org.apache.hertzbeat.common.entity.manager.Param;
|
||||
import org.apache.hertzbeat.common.entity.manager.ParamDefine;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class TextParamValidatorTest {
|
||||
|
||||
private TextParamValidator validator;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
validator = new TextParamValidator();
|
||||
}
|
||||
|
||||
@Test
|
||||
void support() {
|
||||
assertTrue(validator.support("text"));
|
||||
assertTrue(validator.support("textarea"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_ValidLength() {
|
||||
ParamDefine paramDefine = new ParamDefine();
|
||||
paramDefine.setType("text");
|
||||
paramDefine.setLimit((short) 10);
|
||||
Param param = new Param();
|
||||
param.setParamValue("12345");
|
||||
|
||||
assertDoesNotThrow(() -> validator.validate(paramDefine, param));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_OverLimit() {
|
||||
ParamDefine paramDefine = new ParamDefine();
|
||||
paramDefine.setType("text");
|
||||
paramDefine.setField("name");
|
||||
paramDefine.setLimit((short) 3);
|
||||
Param param = new Param();
|
||||
param.setParamValue("12345");
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> validator.validate(paramDefine, param));
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -60,7 +60,7 @@ class MonitorControllerTest {
|
||||
monitor.setJobId(43243543543L);
|
||||
monitor.setName("Api-TanCloud.cn");
|
||||
monitor.setName("TanCloud");
|
||||
monitor.setHost("192.167.25.11");
|
||||
monitor.setInstance("192.167.25.11:8989");
|
||||
monitor.setIntervals(600);
|
||||
monitor.setDescription("对SAAS网站TanCloud的可用性监控");
|
||||
monitor.setCreator("tom");
|
||||
@@ -115,7 +115,7 @@ class MonitorControllerTest {
|
||||
monitor.setJobId(43243543543L);
|
||||
monitor.setName("Api-TanCloud.cn");
|
||||
monitor.setName("TanCloud");
|
||||
monitor.setHost("192.167.25.11");
|
||||
monitor.setInstance("192.167.25.11:8989");
|
||||
monitor.setIntervals(600);
|
||||
monitor.setDescription("对SAAS网站TanCloud的可用性监控");
|
||||
monitor.setCreator("tom");
|
||||
@@ -142,7 +142,7 @@ class MonitorControllerTest {
|
||||
monitor.setJobId(43243543543L);
|
||||
monitor.setName("Api-TanCloud.cn");
|
||||
monitor.setName("TanCloud");
|
||||
monitor.setHost("192.167.25.11");
|
||||
monitor.setInstance("192.167.25.11:8989");
|
||||
monitor.setIntervals(600);
|
||||
monitor.setDescription("对SAAS网站TanCloud的可用性监控");
|
||||
monitor.setCreator("tom");
|
||||
|
||||
+2
-2
@@ -116,7 +116,7 @@ public class CollectorJobSchedulerTest {
|
||||
when(collectorMonitorBindDao.findCollectorMonitorBindsByCollector(identity)).thenReturn(List.of(bind));
|
||||
|
||||
// mock monitor
|
||||
Monitor monitor = Monitor.builder().id(1L).name("test-monitor").host("127.0.0.1").app("test-app").intervals(60).status((byte) 1).build();
|
||||
Monitor monitor = Monitor.builder().id(1L).name("test-monitor").instance("127.0.0.1:8080").app("test-app").intervals(60).status((byte) 1).build();
|
||||
when(monitorDao.findMonitorsByIdIn(any())).thenReturn(List.of(monitor));
|
||||
|
||||
// mock Params
|
||||
@@ -145,7 +145,7 @@ public class CollectorJobSchedulerTest {
|
||||
assertNotNull(job);
|
||||
assertNotNull(job.getMetadata());
|
||||
assertEquals("test-monitor", job.getMetadata().get(CommonConstants.LABEL_INSTANCE_NAME));
|
||||
assertEquals("127.0.0.1", job.getMetadata().get(CommonConstants.LABEL_INSTANCE_HOST));
|
||||
assertEquals("127.0.0.1:8080", job.getMetadata().get(CommonConstants.LABEL_INSTANCE));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+102
-60
@@ -50,9 +50,13 @@ import org.apache.hertzbeat.manager.dao.ParamDao;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.AppCount;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.MonitorDto;
|
||||
import org.apache.hertzbeat.manager.scheduler.CollectJobScheduling;
|
||||
import org.apache.hertzbeat.manager.component.validator.ParamValidatorManager;
|
||||
import org.apache.hertzbeat.manager.service.helper.MonitorImExportHelper;
|
||||
import org.apache.hertzbeat.manager.service.impl.MonitorServiceImpl;
|
||||
import org.apache.hertzbeat.manager.support.exception.MonitorDatabaseException;
|
||||
import org.apache.hertzbeat.manager.support.exception.MonitorDetectException;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -73,13 +77,19 @@ import org.springframework.data.jpa.domain.Specification;
|
||||
* <a href="https://www.cnblogs.com/it1042290135/p/16202478.html">...</a>
|
||||
* <p>
|
||||
* <a href="http://clickhouse:9363/metrics">...</a>
|
||||
* docker run -d --name some-clickhouse-server -p 8123:8123 -p 9009:9009 -p 9090:9000 -p 9363:9363
|
||||
* --ulimit nofile=262144:262144 --volume=/opt/clickhouse/data:/var/lib/clickhouse --volume=/opt/clickhouse/log:/var/log/clickhouse-server
|
||||
* --volume=/opt/clickhouse/conf/config.xml:/etc/clickhouse-server/config.xml --volume=/opt/clickhouse/conf/users.xml:/etc/clickhouse-server/users.xml clickhouse/clickhouse-server
|
||||
* docker run -d --name some-clickhouse-server -p 8123:8123 -p 9009:9009 -p
|
||||
* 9090:9000 -p 9363:9363
|
||||
* --ulimit nofile=262144:262144
|
||||
* --volume=/opt/clickhouse/data:/var/lib/clickhouse
|
||||
* --volume=/opt/clickhouse/log:/var/log/clickhouse-server
|
||||
* --volume=/opt/clickhouse/conf/config.xml:/etc/clickhouse-server/config.xml
|
||||
* --volume=/opt/clickhouse/conf/users.xml:/etc/clickhouse-server/users.xml
|
||||
* clickhouse/clickhouse-server
|
||||
* <p>
|
||||
* <p>
|
||||
* <a href="https://hub.docker.com/r/clickhouse/clickhouse-server/">...</a>
|
||||
* docker run -d -p 18123:8123 -p19000:9000 --name some-clickhouse-server --ulimit nofile=262144:262144 clickhouse/clickhouse-server
|
||||
* docker run -d -p 18123:8123 -p19000:9000 --name some-clickhouse-server
|
||||
* --ulimit nofile=262144:262144 clickhouse/clickhouse-server
|
||||
* curl '<a href="http://localhost:18123/">...</a>'
|
||||
* web UI
|
||||
* <a href="http://localhost:18123/play">...</a>
|
||||
@@ -93,7 +103,13 @@ import org.springframework.data.jpa.domain.Specification;
|
||||
class MonitorServiceTest {
|
||||
|
||||
@InjectMocks
|
||||
private MonitorServiceImpl monitorService = new MonitorServiceImpl(List.of());
|
||||
private MonitorServiceImpl monitorService;
|
||||
|
||||
@Mock
|
||||
private ParamValidatorManager paramValidatorManager;
|
||||
|
||||
@Mock
|
||||
private MonitorImExportHelper monitorImExportHelper;
|
||||
|
||||
@Mock
|
||||
private MonitorDao monitorDao;
|
||||
@@ -142,7 +158,7 @@ class MonitorServiceTest {
|
||||
.intervals(1)
|
||||
.name("memory")
|
||||
.app("demoApp")
|
||||
.host("localhost")
|
||||
.instance("localhost")
|
||||
.build();
|
||||
Job job = new Job();
|
||||
job.setMetrics(new ArrayList<>());
|
||||
@@ -165,7 +181,7 @@ class MonitorServiceTest {
|
||||
.intervals(1)
|
||||
.name("memory")
|
||||
.app("demoApp")
|
||||
.host("localhost")
|
||||
.instance("localhost")
|
||||
.build();
|
||||
Job job = new Job();
|
||||
job.setMetrics(new ArrayList<>());
|
||||
@@ -188,7 +204,7 @@ class MonitorServiceTest {
|
||||
.intervals(1)
|
||||
.name("memory")
|
||||
.app("demoApp")
|
||||
.host("localhost")
|
||||
.instance("localhost")
|
||||
.build();
|
||||
Job job = new Job();
|
||||
when(appService.getAppDefine(monitor.getApp())).thenReturn(job);
|
||||
@@ -204,7 +220,7 @@ class MonitorServiceTest {
|
||||
Monitor monitor = Monitor.builder()
|
||||
.intervals(1)
|
||||
.name("memory")
|
||||
.host("localhost")
|
||||
.instance("localhost")
|
||||
.app("demoApp")
|
||||
.build();
|
||||
Job job = new Job();
|
||||
@@ -223,10 +239,10 @@ class MonitorServiceTest {
|
||||
MonitorDto dto = new MonitorDto();
|
||||
List<Param> params = new ArrayList<>();
|
||||
dto.setParams(params);
|
||||
Monitor monitor = Monitor.builder().name("memory").host("host").id(1L).build();
|
||||
Monitor monitor = Monitor.builder().name("memory").instance("host").id(1L).build();
|
||||
dto.setMonitor(monitor);
|
||||
Boolean isModify = true;
|
||||
Monitor existMonitor = Monitor.builder().name("memory").host("host").id(2L).build();
|
||||
Monitor existMonitor = Monitor.builder().name("memory").instance("host").id(2L).build();
|
||||
when(monitorDao.findMonitorByNameEquals(monitor.getName())).thenReturn(Optional.of(existMonitor));
|
||||
try {
|
||||
monitorService.validate(dto, isModify);
|
||||
@@ -249,10 +265,10 @@ class MonitorServiceTest {
|
||||
.build();
|
||||
params.add(param);
|
||||
dto.setParams(params);
|
||||
Monitor monitor = Monitor.builder().name("memory").host("host").id(1L).build();
|
||||
Monitor monitor = Monitor.builder().name("memory").instance("host").id(1L).build();
|
||||
dto.setMonitor(monitor);
|
||||
Boolean isModify = true;
|
||||
Monitor existMonitor = Monitor.builder().name("memory").host("host").id(1L).build();
|
||||
Monitor existMonitor = Monitor.builder().name("memory").instance("host").id(1L).build();
|
||||
when(monitorDao.findMonitorByNameEquals(monitor.getName())).thenReturn(Optional.of(existMonitor));
|
||||
List<ParamDefine> paramDefines = new ArrayList<>();
|
||||
ParamDefine pd = ParamDefine.builder()
|
||||
@@ -282,10 +298,10 @@ class MonitorServiceTest {
|
||||
.build();
|
||||
params.add(param);
|
||||
dto.setParams(params);
|
||||
Monitor monitor = Monitor.builder().name("memory").host("host").id(1L).build();
|
||||
Monitor monitor = Monitor.builder().name("memory").instance("host").id(1L).build();
|
||||
dto.setMonitor(monitor);
|
||||
Boolean isModify = true;
|
||||
Monitor existMonitor = Monitor.builder().name("memory").host("host").id(1L).build();
|
||||
Monitor existMonitor = Monitor.builder().name("memory").instance("host").id(1L).build();
|
||||
when(monitorDao.findMonitorByNameEquals(monitor.getName())).thenReturn(Optional.of(existMonitor));
|
||||
List<ParamDefine> paramDefines = new ArrayList<>();
|
||||
ParamDefine paramDefine = ParamDefine.builder()
|
||||
@@ -296,6 +312,8 @@ class MonitorServiceTest {
|
||||
.build();
|
||||
paramDefines.add(paramDefine);
|
||||
when(appService.getAppParamDefines(monitor.getApp())).thenReturn(paramDefines);
|
||||
doThrow(new IllegalArgumentException("Params field " + field + " type "
|
||||
+ paramDefine.getType() + " is invalid.")).when(paramValidatorManager).validate(any(), any());
|
||||
try {
|
||||
monitorService.validate(dto, isModify);
|
||||
} catch (IllegalArgumentException e) {
|
||||
@@ -305,7 +323,8 @@ class MonitorServiceTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parameter verification - This parameter is mandatory. - Integer parameter range
|
||||
* Parameter verification - This parameter is mandatory. - Integer parameter
|
||||
* range
|
||||
*/
|
||||
@Test
|
||||
void validateMonitorParamsRange() {
|
||||
@@ -318,10 +337,10 @@ class MonitorServiceTest {
|
||||
.build();
|
||||
params.add(param);
|
||||
dto.setParams(params);
|
||||
Monitor monitor = Monitor.builder().name("memory").host("host").id(1L).build();
|
||||
Monitor monitor = Monitor.builder().name("memory").instance("host").id(1L).build();
|
||||
dto.setMonitor(monitor);
|
||||
Boolean isModify = true;
|
||||
Monitor existMonitor = Monitor.builder().name("memory").host("host").id(1L).build();
|
||||
Monitor existMonitor = Monitor.builder().name("memory").instance("host").id(1L).build();
|
||||
when(monitorDao.findMonitorByNameEquals(monitor.getName())).thenReturn(Optional.of(existMonitor));
|
||||
List<ParamDefine> paramDefines = new ArrayList<>();
|
||||
ParamDefine paramDefine = ParamDefine.builder()
|
||||
@@ -332,6 +351,9 @@ class MonitorServiceTest {
|
||||
.build();
|
||||
paramDefines.add(paramDefine);
|
||||
when(appService.getAppParamDefines(monitor.getApp())).thenReturn(paramDefines);
|
||||
doThrow(new IllegalArgumentException("Params field " + field + " type "
|
||||
+ paramDefine.getType() + " over range " + paramDefine.getRange())).when(paramValidatorManager)
|
||||
.validate(any(), any());
|
||||
try {
|
||||
monitorService.validate(dto, isModify);
|
||||
} catch (IllegalArgumentException e) {
|
||||
@@ -354,10 +376,10 @@ class MonitorServiceTest {
|
||||
.build();
|
||||
params.add(param);
|
||||
dto.setParams(params);
|
||||
Monitor monitor = Monitor.builder().name("memory").host("host").id(1L).build();
|
||||
Monitor monitor = Monitor.builder().name("memory").instance("host").id(1L).build();
|
||||
dto.setMonitor(monitor);
|
||||
Boolean isModify = true;
|
||||
Monitor existMonitor = Monitor.builder().name("memory").host("host").id(1L).build();
|
||||
Monitor existMonitor = Monitor.builder().name("memory").instance("host").id(1L).build();
|
||||
when(monitorDao.findMonitorByNameEquals(monitor.getName())).thenReturn(Optional.of(existMonitor));
|
||||
List<ParamDefine> paramDefines = new ArrayList<>();
|
||||
Short limit = 3;
|
||||
@@ -369,6 +391,8 @@ class MonitorServiceTest {
|
||||
.build();
|
||||
paramDefines.add(paramDefine);
|
||||
when(appService.getAppParamDefines(monitor.getApp())).thenReturn(paramDefines);
|
||||
doThrow(new IllegalArgumentException("Params field " + field + " type "
|
||||
+ paramDefine.getType() + " over limit " + limit)).when(paramValidatorManager).validate(any(), any());
|
||||
try {
|
||||
monitorService.validate(dto, isModify);
|
||||
} catch (IllegalArgumentException e) {
|
||||
@@ -398,10 +422,10 @@ class MonitorServiceTest {
|
||||
.build();
|
||||
params.add(param);
|
||||
dto.setParams(params);
|
||||
Monitor monitor = Monitor.builder().name("memory").host("host").id(1L).build();
|
||||
Monitor monitor = Monitor.builder().name("memory").instance("host").id(1L).build();
|
||||
dto.setMonitor(monitor);
|
||||
Boolean isModify = true;
|
||||
Monitor existMonitor = Monitor.builder().name("memory").host("host").id(1L).build();
|
||||
Monitor existMonitor = Monitor.builder().name("memory").instance("host").id(1L).build();
|
||||
when(monitorDao.findMonitorByNameEquals(monitor.getName())).thenReturn(Optional.of(existMonitor));
|
||||
List<ParamDefine> paramDefines = new ArrayList<>();
|
||||
Short limit = 3;
|
||||
@@ -413,6 +437,11 @@ class MonitorServiceTest {
|
||||
.build();
|
||||
paramDefines.add(paramDefine);
|
||||
when(appService.getAppParamDefines(monitor.getApp())).thenReturn(paramDefines);
|
||||
if (checkException) {
|
||||
doThrow(new IllegalArgumentException(
|
||||
"Params field " + field + " value " + value + " is invalid host value."))
|
||||
.when(paramValidatorManager).validate(any(), any());
|
||||
}
|
||||
try {
|
||||
monitorService.validate(dto, isModify);
|
||||
} catch (IllegalArgumentException e) {
|
||||
@@ -443,10 +472,10 @@ class MonitorServiceTest {
|
||||
.build();
|
||||
params.add(param);
|
||||
dto.setParams(params);
|
||||
Monitor monitor = Monitor.builder().name("memory").host("host").id(1L).build();
|
||||
Monitor monitor = Monitor.builder().name("memory").instance("host").id(1L).build();
|
||||
dto.setMonitor(monitor);
|
||||
Boolean isModify = true;
|
||||
Monitor existMonitor = Monitor.builder().name("memory").host("host").id(1L).build();
|
||||
Monitor existMonitor = Monitor.builder().name("memory").instance("host").id(1L).build();
|
||||
when(monitorDao.findMonitorByNameEquals(monitor.getName())).thenReturn(Optional.of(existMonitor));
|
||||
List<ParamDefine> paramDefines = new ArrayList<>();
|
||||
Short limit = 3;
|
||||
@@ -459,6 +488,10 @@ class MonitorServiceTest {
|
||||
.build();
|
||||
paramDefines.add(paramDefine);
|
||||
when(appService.getAppParamDefines(monitor.getApp())).thenReturn(paramDefines);
|
||||
if (checkException) {
|
||||
doThrow(new IllegalArgumentException("Params field " + field + " value "
|
||||
+ value + " is invalid boolean value.")).when(paramValidatorManager).validate(any(), any());
|
||||
}
|
||||
try {
|
||||
monitorService.validate(dto, isModify);
|
||||
} catch (IllegalArgumentException e) {
|
||||
@@ -490,10 +523,10 @@ class MonitorServiceTest {
|
||||
.build();
|
||||
params.add(param);
|
||||
dto.setParams(params);
|
||||
Monitor monitor = Monitor.builder().name("memory").host("host").id(1L).build();
|
||||
Monitor monitor = Monitor.builder().name("memory").instance("host").id(1L).build();
|
||||
dto.setMonitor(monitor);
|
||||
Boolean isModify = true;
|
||||
Monitor existMonitor = Monitor.builder().name("memory").host("host").id(1L).build();
|
||||
Monitor existMonitor = Monitor.builder().name("memory").instance("host").id(1L).build();
|
||||
when(monitorDao.findMonitorByNameEquals(monitor.getName())).thenReturn(Optional.of(existMonitor));
|
||||
List<ParamDefine> paramDefines = new ArrayList<>();
|
||||
Short limit = 3;
|
||||
@@ -510,6 +543,11 @@ class MonitorServiceTest {
|
||||
.build();
|
||||
paramDefines.add(paramDefine);
|
||||
when(appService.getAppParamDefines(monitor.getApp())).thenReturn(paramDefines);
|
||||
if (checkException) {
|
||||
doThrow(new IllegalArgumentException("Params field " + field + " value "
|
||||
+ param.getParamValue() + " is invalid option value")).when(paramValidatorManager)
|
||||
.validate(any(), any());
|
||||
}
|
||||
try {
|
||||
monitorService.validate(dto, isModify);
|
||||
} catch (IllegalArgumentException e) {
|
||||
@@ -541,10 +579,10 @@ class MonitorServiceTest {
|
||||
.build();
|
||||
params.add(param);
|
||||
dto.setParams(params);
|
||||
Monitor monitor = Monitor.builder().name("memory").host("host").id(1L).build();
|
||||
Monitor monitor = Monitor.builder().name("memory").instance("host").id(1L).build();
|
||||
dto.setMonitor(monitor);
|
||||
Boolean isModify = true;
|
||||
Monitor existMonitor = Monitor.builder().name("memory").host("host").id(1L).build();
|
||||
Monitor existMonitor = Monitor.builder().name("memory").instance("host").id(1L).build();
|
||||
when(monitorDao.findMonitorByNameEquals(monitor.getName())).thenReturn(Optional.of(existMonitor));
|
||||
List<ParamDefine> paramDefines = new ArrayList<>();
|
||||
Short limit = 3;
|
||||
@@ -561,6 +599,10 @@ class MonitorServiceTest {
|
||||
.build();
|
||||
paramDefines.add(paramDefine);
|
||||
when(appService.getAppParamDefines(monitor.getApp())).thenReturn(paramDefines);
|
||||
if (checkException) {
|
||||
doThrow(new IllegalArgumentException("ParamDefine type " + paramDefine.getType() + " is invalid."))
|
||||
.when(paramValidatorManager).validate(any(), any());
|
||||
}
|
||||
try {
|
||||
monitorService.validate(dto, isModify);
|
||||
} catch (IllegalArgumentException e) {
|
||||
@@ -584,7 +626,8 @@ class MonitorServiceTest {
|
||||
params.add(param);
|
||||
dto.setParams(params);
|
||||
long monitorId = 1L;
|
||||
Monitor monitor = Monitor.builder().jobId(1L).intervals(1).app("app").name("memory").host("host").id(monitorId).build();
|
||||
Monitor monitor = Monitor.builder().jobId(1L).intervals(1).app("app").name("memory").instance("host")
|
||||
.id(monitorId).build();
|
||||
dto.setMonitor(monitor);
|
||||
when(monitorDao.findById(monitorId)).thenReturn(Optional.empty());
|
||||
try {
|
||||
@@ -594,9 +637,9 @@ class MonitorServiceTest {
|
||||
}
|
||||
reset();
|
||||
/*
|
||||
The [monitoring type] of monitor cannot be modified.
|
||||
* The [monitoring type] of monitor cannot be modified.
|
||||
*/
|
||||
Monitor existErrorMonitor = Monitor.builder().app("app2").name("memory").host("host").id(monitorId).build();
|
||||
Monitor existErrorMonitor = Monitor.builder().app("app2").name("memory").instance("host").id(monitorId).build();
|
||||
when(monitorDao.findById(monitorId)).thenReturn(Optional.of(existErrorMonitor));
|
||||
try {
|
||||
monitorService.modifyMonitor(dto.getMonitor(), dto.getParams(), null, null);
|
||||
@@ -604,11 +647,13 @@ class MonitorServiceTest {
|
||||
assertEquals("Can not modify monitor's app type", e.getMessage());
|
||||
}
|
||||
reset();
|
||||
Monitor existOkMonitor = Monitor.builder().jobId(1L).intervals(1).app("app").name("memory").host("host").id(monitorId).build();
|
||||
Monitor existOkMonitor = Monitor.builder().jobId(1L).intervals(1).app("app").name("memory").instance("host")
|
||||
.id(monitorId).build();
|
||||
when(monitorDao.findById(monitorId)).thenReturn(Optional.of(existOkMonitor));
|
||||
when(monitorDao.save(monitor)).thenThrow(RuntimeException.class);
|
||||
|
||||
assertThrows(MonitorDatabaseException.class, () -> monitorService.modifyMonitor(dto.getMonitor(), dto.getParams(), null, null));
|
||||
assertThrows(MonitorDatabaseException.class,
|
||||
() -> monitorService.modifyMonitor(dto.getMonitor(), dto.getParams(), null, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -617,7 +662,8 @@ class MonitorServiceTest {
|
||||
ids.add(1L);
|
||||
List<Monitor> monitors = new ArrayList<>();
|
||||
for (Long id : ids) {
|
||||
Monitor monitor = Monitor.builder().jobId(id).intervals(1).app("app").name("memory").host("host").id(id).build();
|
||||
Monitor monitor = Monitor.builder().jobId(id).intervals(1).app("app").name("memory").instance("host").id(id)
|
||||
.build();
|
||||
monitors.add(monitor);
|
||||
}
|
||||
when(monitorDao.findMonitorsByIdIn(ids)).thenReturn(monitors);
|
||||
@@ -633,7 +679,8 @@ class MonitorServiceTest {
|
||||
|
||||
List<Monitor> monitors = new ArrayList<>();
|
||||
for (Long id : ids) {
|
||||
Monitor monitor = Monitor.builder().jobId(id).intervals(1).app("app").name("memory").host("host").id(id).build();
|
||||
Monitor monitor = Monitor.builder().jobId(id).intervals(1).app("app").name("memory").instance("host").id(id)
|
||||
.build();
|
||||
monitors.add(monitor);
|
||||
}
|
||||
when(monitorDao.findMonitorsByIdIn(ids)).thenReturn(monitors);
|
||||
@@ -643,7 +690,8 @@ class MonitorServiceTest {
|
||||
@Test
|
||||
void getMonitorDto() {
|
||||
long id = 1L;
|
||||
Monitor monitor = Monitor.builder().jobId(id).intervals(1).app("app").name("memory").host("host").id(id).build();
|
||||
Monitor monitor = Monitor.builder().jobId(id).intervals(1).app("app").name("memory").instance("host").id(id)
|
||||
.build();
|
||||
when(monitorDao.findById(id)).thenReturn(Optional.of(monitor));
|
||||
List<Param> params = Collections.singletonList(new Param());
|
||||
when(paramDao.findParamsByMonitorId(id)).thenReturn(params);
|
||||
@@ -657,7 +705,7 @@ class MonitorServiceTest {
|
||||
|
||||
@Test
|
||||
void getMonitors() {
|
||||
when(monitorDao.findAll(any(Specification.class), any(PageRequest.class))).thenAnswer((invocation)->{
|
||||
when(monitorDao.findAll(any(Specification.class), any(PageRequest.class))).thenAnswer((invocation) -> {
|
||||
Specification<Monitor> spec = invocation.getArgument(0);
|
||||
CriteriaBuilder cb = mock(CriteriaBuilder.class);
|
||||
CriteriaQuery<?> query = mock(CriteriaQuery.class);
|
||||
@@ -677,7 +725,8 @@ class MonitorServiceTest {
|
||||
|
||||
List<Monitor> monitors = new ArrayList<>();
|
||||
for (Long id : ids) {
|
||||
Monitor monitor = Monitor.builder().jobId(id).intervals(1).app("app").name("memory").host("host").id(id).build();
|
||||
Monitor monitor = Monitor.builder().jobId(id).intervals(1).app("app").name("memory").instance("host").id(id)
|
||||
.build();
|
||||
monitors.add(monitor);
|
||||
}
|
||||
when(monitorDao.findMonitorsByIdIn(ids)).thenReturn(monitors);
|
||||
@@ -692,7 +741,8 @@ class MonitorServiceTest {
|
||||
|
||||
List<Monitor> monitors = new ArrayList<>();
|
||||
for (Long id : ids) {
|
||||
Monitor monitor = Monitor.builder().jobId(id).intervals(1).app("app").name("memory").host("host").id(id).build();
|
||||
Monitor monitor = Monitor.builder().jobId(id).intervals(1).app("app").name("memory").instance("host").id(id)
|
||||
.build();
|
||||
monitor.setStatus(CommonConstants.MONITOR_PAUSED_CODE);
|
||||
monitors.add(monitor);
|
||||
}
|
||||
@@ -716,7 +766,6 @@ class MonitorServiceTest {
|
||||
appCounts.add(appCount);
|
||||
when(monitorDao.findAppsStatusCount()).thenReturn(appCounts);
|
||||
|
||||
|
||||
Job job = new Job();
|
||||
job.setMetrics(new ArrayList<>());
|
||||
when(appService.getAppDefine(appCounts.get(0).getApp())).thenReturn(job);
|
||||
@@ -752,7 +801,7 @@ class MonitorServiceTest {
|
||||
.intervals(1)
|
||||
.name("memory")
|
||||
.app("demoApp")
|
||||
.host("localhost")
|
||||
.instance("localhost")
|
||||
.build();
|
||||
Job job = new Job();
|
||||
when(appService.getAppDefine(monitor.getApp())).thenReturn(job);
|
||||
@@ -768,26 +817,17 @@ class MonitorServiceTest {
|
||||
Monitor monitor1 = Monitor.builder().id(1L).name("test1").app("app1").build();
|
||||
Monitor monitor2 = Monitor.builder().id(2L).name("test2").app("app2").build();
|
||||
List<Monitor> allMonitors = List.of(monitor1, monitor2);
|
||||
|
||||
|
||||
// Mock the behavior of monitorDao.findAll
|
||||
when(monitorDao.findAll()).thenReturn(allMonitors);
|
||||
|
||||
|
||||
// Create a mock HttpServletResponse
|
||||
jakarta.servlet.http.HttpServletResponse mockResponse = org.mockito.Mockito.mock(jakarta.servlet.http.HttpServletResponse.class);
|
||||
|
||||
// Mock the ImExportService
|
||||
org.apache.hertzbeat.manager.service.ImExportService mockImExportService = org.mockito.Mockito.mock(org.apache.hertzbeat.manager.service.ImExportService.class);
|
||||
// Mock the getFileName method
|
||||
when(mockImExportService.getFileName()).thenReturn("test.json");
|
||||
// Set the field using reflection
|
||||
java.lang.reflect.Field field = MonitorServiceImpl.class.getDeclaredField("imExportServiceMap");
|
||||
field.setAccessible(true);
|
||||
java.util.Map<String, org.apache.hertzbeat.manager.service.ImExportService> imExportServiceMap = new java.util.HashMap<>();
|
||||
imExportServiceMap.put("JSON", mockImExportService);
|
||||
field.set(monitorService, imExportServiceMap);
|
||||
|
||||
jakarta.servlet.http.HttpServletResponse mockResponse = org.mockito.Mockito
|
||||
.mock(jakarta.servlet.http.HttpServletResponse.class);
|
||||
|
||||
// Test the exportAll method
|
||||
assertDoesNotThrow(() -> monitorService.exportAll("JSON", mockResponse));
|
||||
verify(monitorImExportHelper).export(List.of(1L, 2L), "JSON", mockResponse);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -802,8 +842,8 @@ class MonitorServiceTest {
|
||||
Job job = new Job();
|
||||
job.setApp("testJob");
|
||||
job.setMetrics(metrics);
|
||||
Monitor monitor = Monitor.builder().jobId(1L).intervals(1).app(job.getApp()).name(job.getApp()).host("host").build();
|
||||
|
||||
Monitor monitor = Monitor.builder().jobId(1L).intervals(1).app(job.getApp()).name(job.getApp()).instance("host")
|
||||
.build();
|
||||
|
||||
List<Param> params = new ArrayList<>();
|
||||
params.add(Param.builder().field("field").paramValue("value").build());
|
||||
@@ -813,7 +853,9 @@ class MonitorServiceTest {
|
||||
dto.setParams(params);
|
||||
|
||||
when(appService.getAppDefine(monitor.getApp())).thenReturn(job);
|
||||
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> monitorService.validate(dto, null));
|
||||
assertEquals("testJob metricsName size prohibited keywords, please modify the template information.", exception.getMessage());
|
||||
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
|
||||
() -> monitorService.validate(dto, null));
|
||||
assertEquals("testJob metricsName size prohibited keywords, please modify the template information.",
|
||||
exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.manager.service.helper;
|
||||
|
||||
import jakarta.servlet.ServletOutputStream;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.apache.hertzbeat.manager.config.ManagerSseManager;
|
||||
import org.apache.hertzbeat.manager.service.ImExportService;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyList;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class MonitorImExportHelperTest {
|
||||
|
||||
private MonitorImExportHelper helper;
|
||||
|
||||
@Mock
|
||||
private ImExportService imExportService;
|
||||
|
||||
@Mock
|
||||
private ManagerSseManager managerSseManager;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
when(imExportService.type()).thenReturn("JSON");
|
||||
helper = new MonitorImExportHelper(List.of(imExportService), managerSseManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
void export_Success() throws Exception {
|
||||
HttpServletResponse response = mock(HttpServletResponse.class);
|
||||
ServletOutputStream outputStream = mock(ServletOutputStream.class);
|
||||
when(response.getOutputStream()).thenReturn(outputStream);
|
||||
when(imExportService.getFileName()).thenReturn("test.json");
|
||||
|
||||
helper.export(List.of(1L), "JSON", response);
|
||||
|
||||
verify(imExportService).exportConfig(eq(outputStream), anyList());
|
||||
}
|
||||
|
||||
@Test
|
||||
void export_UnsupportedType() {
|
||||
HttpServletResponse response = mock(HttpServletResponse.class);
|
||||
assertThrows(IllegalArgumentException.class, () -> helper.export(List.of(1L), "XML", response));
|
||||
}
|
||||
|
||||
@Test
|
||||
void importConfig_Success() throws Exception {
|
||||
MultipartFile file = mock(MultipartFile.class);
|
||||
when(file.getOriginalFilename()).thenReturn("test.json");
|
||||
InputStream inputStream = new ByteArrayInputStream("{}".getBytes());
|
||||
when(file.getInputStream()).thenReturn(inputStream);
|
||||
|
||||
helper.importConfig(file);
|
||||
|
||||
verify(imExportService).importConfig(eq("test.json"), any(InputStream.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void importConfig_UnsupportedType() {
|
||||
MultipartFile file = mock(MultipartFile.class);
|
||||
when(file.getOriginalFilename()).thenReturn("test.xml");
|
||||
|
||||
assertThrows(RuntimeException.class, () -> helper.importConfig(file));
|
||||
verify(managerSseManager).broadcastImportTaskFail(eq("test.xml"), any());
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user