mirror of
https://github.com/dromara/hertzbeat.git
synced 2026-09-17 18:19:02 +00:00
Compare commits
120
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c33aa86ba2 | ||
|
|
b6bca5a10d | ||
|
|
c6f1f07155 | ||
|
|
0ebd3499b7 | ||
|
|
c88d4a7b0e | ||
|
|
8bebae9db6 | ||
|
|
b6b1a48a5c | ||
|
|
63ed50ef45 | ||
|
|
e63715d1b0 | ||
|
|
edf3eac6d6 | ||
|
|
743f285c8d | ||
|
|
458433464c | ||
|
|
64740a4f85 | ||
|
|
b5a2bce261 | ||
|
|
5e351c854b | ||
|
|
0875bea046 | ||
|
|
9ad5f77816 | ||
|
|
998f9059bc | ||
|
|
a1439c3ec2 | ||
|
|
742b6f6755 | ||
|
|
5fdb34598c | ||
|
|
4f982d1ab0 | ||
|
|
4a28056d9f | ||
|
|
3f2fa71e22 | ||
|
|
08c81bf62f | ||
|
|
e16d6cb4ba | ||
|
|
a54d14e803 | ||
|
|
01ae281769 | ||
|
|
9cb10ff14d | ||
|
|
62f40d3ad1 | ||
|
|
57601ef989 | ||
|
|
0a05edbec5 | ||
|
|
fcef24fdbf | ||
|
|
e45c31abfb | ||
|
|
198c190ca7 | ||
|
|
392a59f9a9 | ||
|
|
c01b7f7bce | ||
|
|
0323b044b8 | ||
|
|
256302fdcf | ||
|
|
3f3330af41 | ||
|
|
33342baa3f | ||
|
|
563cbd7943 | ||
|
|
ddb1601290 | ||
|
|
8e9d3c09f3 | ||
|
|
ade04d2cf2 | ||
|
|
53555c88c7 | ||
|
|
03bf31269d | ||
|
|
5c329a8a66 | ||
|
|
dda9250243 | ||
|
|
65a97aed8f | ||
|
|
56528f4fc0 | ||
|
|
f218ba018a | ||
|
|
d6ce0c153a | ||
|
|
a1b23cacdc | ||
|
|
ed6214d0c1 | ||
|
|
9a3fc5911d | ||
|
|
d5d01459e7 | ||
|
|
d077c72211 | ||
|
|
11e3e4cc22 | ||
|
|
c24c6ab7aa | ||
|
|
a183176e83 | ||
|
|
a2e8739fec | ||
|
|
ebb6e3d4ff | ||
|
|
fc3f26368d | ||
|
|
7967cfabf0 | ||
|
|
199466cd59 | ||
|
|
8065b63057 | ||
|
|
88655fcf19 | ||
|
|
99b5460dd1 | ||
|
|
d681420c11 | ||
|
|
8618145e44 | ||
|
|
dd0045c6a4 | ||
|
|
84e0a604e7 | ||
|
|
ce253f7069 | ||
|
|
767b70f70b | ||
|
|
4aad08ca17 | ||
|
|
c176bb2988 | ||
|
|
3cb4fc71f9 | ||
|
|
c3f32132ca | ||
|
|
e1affb169f | ||
|
|
db27159947 | ||
|
|
63c187488f | ||
|
|
f3f66ada0d | ||
|
|
d5c934dc01 | ||
|
|
518eebb6b1 | ||
|
|
210454f969 | ||
|
|
a8db5e31a0 | ||
|
|
c01653aff3 | ||
|
|
480bd0e449 | ||
|
|
97faca6753 | ||
|
|
e271bd7417 | ||
|
|
7de4fd1cbf | ||
|
|
c2a7d0db35 | ||
|
|
93b2630bf3 | ||
|
|
23749e7fac | ||
|
|
d5eae40754 | ||
|
|
7ef79de1de | ||
|
|
8496771c8b | ||
|
|
f31c835181 | ||
|
|
552554b96d | ||
|
|
4adcfd2a4e | ||
|
|
e630beecb4 | ||
|
|
6c2b38e180 | ||
|
|
84986dd1bf | ||
|
|
af868e083a | ||
|
|
be3e2f3558 | ||
|
|
d32818e8ef | ||
|
|
10b283522c | ||
|
|
31ab2da41e | ||
|
|
a9436049e3 | ||
|
|
41c010511c | ||
|
|
b5281c1274 | ||
|
|
62cf3d1b2b | ||
|
|
dc8ae844c1 | ||
|
|
91a7593b87 | ||
|
|
4442a52adf | ||
|
|
60e3437e82 | ||
|
|
21d6ec2f1b | ||
|
|
3ed50f39cb | ||
|
|
dfba21f224 |
@@ -2485,6 +2485,25 @@
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Duansg",
|
||||
"name": "Duansg",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/112607719?v=4",
|
||||
"profile": "https://github.com/Duansg",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "xiaomizhou2",
|
||||
"name": "zhangyaxi",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/47807926?v=4",
|
||||
"profile": "https://github.com/xiaomizhou2",
|
||||
"contributions": [
|
||||
"code",
|
||||
"test"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
||||
@@ -52,6 +52,9 @@ github:
|
||||
required_pull_request_reviews:
|
||||
dismiss_stale_reviews: true
|
||||
required_approving_review_count: 1
|
||||
# enable GitHub Dependabot to create PRs for security alerts but not for every dependency update
|
||||
dependabot_alerts: true
|
||||
dependabot_updates: false
|
||||
notifications:
|
||||
commits: notifications@hertzbeat.apache.org
|
||||
issues: notifications@hertzbeat.apache.org
|
||||
|
||||
@@ -53,4 +53,5 @@ body:
|
||||
validations:
|
||||
required: false
|
||||
- type: markdown
|
||||
value: "Please read the [Contribution Guideline](https://hertzbeat.apache.org/docs/community/contribution) before submitting the PR"
|
||||
attributes:
|
||||
value: "Please read the [Contribution Guideline](https://hertzbeat.apache.org/docs/community/contribution) before submitting the PR"
|
||||
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
- uses: ./script/ci/github-actions/setup-deps
|
||||
|
||||
- name: Build with Maven
|
||||
run: mvn clean -B package -Prelease -Dmaven.test.skip=false --file pom.xml
|
||||
run: mvnd clean -B package -Prelease -Dmaven.test.skip=false --file pom.xml
|
||||
|
||||
- name: Upload coverage reports to Codecov
|
||||
uses: codecov/codecov-action@v4.0.1
|
||||
|
||||
@@ -49,11 +49,9 @@ jobs:
|
||||
- name: Dead Link Check
|
||||
run: |
|
||||
sudo npm install -g markdown-link-check@3.8.7
|
||||
for file in $(find ./home -name "*.md"); do
|
||||
if ! grep -Fxq "$file" ./script/ci/exclude_files.txt; then
|
||||
markdown-link-check -c ./script/ci/link_check.json -q "$file"
|
||||
fi
|
||||
done
|
||||
find ./home -name "*.md" > all_md_files.txt
|
||||
grep -vFf ./script/ci/exclude_files.txt all_md_files.txt > to_check.txt
|
||||
xargs -P 8 -a to_check.txt -I{} markdown-link-check -c ./script/ci/link_check.json -q "{}"
|
||||
|
||||
- name: NPM INSTALL
|
||||
working-directory: home
|
||||
|
||||
@@ -523,6 +523,10 @@ Thanks to these wonderful people, welcome to join us:
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/boyucjz"><img src="https://avatars.githubusercontent.com/u/18730041?v=4?s=100" width="100px;" alt="boyucjz"/><br /><sub><b>boyucjz</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=boyucjz" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Cyanty"><img src="https://avatars.githubusercontent.com/u/153884653?v=4?s=100" width="100px;" alt="Cyanty"/><br /><sub><b>Cyanty</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Cyanty" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=Cyanty" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/KevinLLF"><img src="https://avatars.githubusercontent.com/u/85452733?v=4?s=100" width="100px;" alt="Jay丿167"/><br /><sub><b>Jay丿167</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=KevinLLF" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Duansg"><img src="https://avatars.githubusercontent.com/u/112607719?v=4?s=100" width="100px;" alt="Duansg"/><br /><sub><b>Duansg</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Duansg" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/xiaomizhou2"><img src="https://avatars.githubusercontent.com/u/47807926?v=4?s=100" width="100px;" alt="zhangyaxi"/><br /><sub><b>zhangyaxi</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=xiaomizhou2" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=xiaomizhou2" title="Tests">⚠️</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -544,7 +548,7 @@ WeChat Group : Add friend `ahertzbeat` and invite to the group.
|
||||
|
||||
WeChat Public : Search ID `usthecom`.
|
||||
|
||||
[QQ Group](https://jq.qq.com/?_wv=1027&k=Bud9OzdI) : Group num `630061200`
|
||||
[QQ Group](https://qm.qq.com/q/xxqecSC2cw) : Group num `1035688434`
|
||||
|
||||
[Github Discussion](https://github.com/apache/hertzbeat/discussions)
|
||||
|
||||
|
||||
+10
-6
@@ -33,8 +33,8 @@
|
||||
### 特点
|
||||
|
||||
- 集 **监控+告警+通知** 为一体,支持对应用服务,应用程序,数据库,缓存,操作系统,大数据,中间件,Web 服务器,云原生,网络,自定义等监控阈值告警通知一步到位。
|
||||
- 易用友好,无需 `Agent`,全 `WEB` 页面操作,鼠标点一点就能监控告警,零上手学习成本。
|
||||
- 将 `Http, Jmx, Ssh, Snmp, Jdbc, Prometheus` 等协议规范可配置化,只需在浏览器配置监控模版 `YML` 就能使用这些协议去自定义采集想要的指标。您相信只需配置下就能立刻适配一款 `K8s` 或 `Docker` 等新的监控类型吗?
|
||||
- 易用友好,无需 `Agent`,全 `WEB` 页面操作,鼠标点一点就能监控告警,无需学习成本。
|
||||
- 将 `Http, Jmx, Ssh, Snmp, Jdbc, Prometheus` 等协议规范可配置化,只需在浏览器配置监控模板 `YML` 就能使用这些协议去自定义采集想要的指标。您相信只需简单配置即可快速适配一款 `K8s` 或 `Docker` 等新的监控类型吗?
|
||||
- 兼容 `Prometheus` 的系统生态并且更多,只需页面操作就可以监控 `Prometheus` 所能监控的。
|
||||
- 高性能,支持多采集器集群横向扩展,支持多隔离网络监控,云边协同。
|
||||
- 自由的告警阈值规则,`邮件` `Discord` `Slack` `Telegram` `钉钉` `微信` `飞书` `短信` `Webhook` `Server酱` 等方式消息及时送达。
|
||||
@@ -59,8 +59,8 @@
|
||||
|
||||
## ⛄ 已支持
|
||||
|
||||
> 我们将监控采集类型(mysql,jvm,k8s)都定义为 `yml` 监控模版,用户可以导入这些模版来支持对应类型的监控!
|
||||
> 欢迎大家一起贡献你使用过程中自定义的通用监控类型监控模版。
|
||||
> 我们将监控采集类型(mysql,jvm,k8s)都定义为 `yml` 监控模板,用户可以导入这些模板来支持对应类型的监控!
|
||||
> 欢迎大家一起贡献你使用过程中自定义的通用监控类型监控模板。
|
||||
|
||||
- [Website](https://raw.githubusercontent.com/apache/hertzbeat/master/hertzbeat-manager/src/main/resources/define/app-website.yml), [Port Telnet](https://raw.githubusercontent.com/apache/hertzbeat/master/hertzbeat-manager/src/main/resources/define/app-port.yml),
|
||||
[Http Api](https://raw.githubusercontent.com/apache/hertzbeat/master/hertzbeat-manager/src/main/resources/define/app-api.yml), [Ping Connect](https://raw.githubusercontent.com/apache/hertzbeat/master/hertzbeat-manager/src/main/resources/define/app-ping.yml),
|
||||
@@ -98,7 +98,7 @@
|
||||
- [CiscoSwitch](https://raw.githubusercontent.com/apache/hertzbeat/master/hertzbeat-manager/src/main/resources/define/app-cisco_switch.yml), [HpeSwitch](https://raw.githubusercontent.com/apache/hertzbeat/master/hertzbeat-manager/src/main/resources/define/app-hpe_switch.yml),
|
||||
[HuaweiSwitch](https://raw.githubusercontent.com/apache/hertzbeat/master/hertzbeat-manager/src/main/resources/define/app-huawei_switch.yml), [TpLinkSwitch](https://raw.githubusercontent.com/apache/hertzbeat/master/hertzbeat-manager/src/main/resources/define/app-tplink_switch.yml),
|
||||
[H3cSwitch](https://raw.githubusercontent.com/apache/hertzbeat/master/hertzbeat-manager/src/main/resources/define/app-h3c_switch.yml)
|
||||
- 和更多自定义监控模版。
|
||||
- 和更多自定义监控模板。
|
||||
- 通知支持 `Discord` `Slack` `Telegram` `邮件` `钉钉` `微信` `飞书` `短信` `Webhook` `Server酱`。
|
||||
|
||||
## 🐕 快速开始
|
||||
@@ -521,6 +521,10 @@ Thanks these wonderful people, welcome to join us:
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/boyucjz"><img src="https://avatars.githubusercontent.com/u/18730041?v=4?s=100" width="100px;" alt="boyucjz"/><br /><sub><b>boyucjz</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=boyucjz" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Cyanty"><img src="https://avatars.githubusercontent.com/u/153884653?v=4?s=100" width="100px;" alt="Cyanty"/><br /><sub><b>Cyanty</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Cyanty" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=Cyanty" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/KevinLLF"><img src="https://avatars.githubusercontent.com/u/85452733?v=4?s=100" width="100px;" alt="Jay丿167"/><br /><sub><b>Jay丿167</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=KevinLLF" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Duansg"><img src="https://avatars.githubusercontent.com/u/112607719?v=4?s=100" width="100px;" alt="Duansg"/><br /><sub><b>Duansg</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Duansg" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/xiaomizhou2"><img src="https://avatars.githubusercontent.com/u/47807926?v=4?s=100" width="100px;" alt="zhangyaxi"/><br /><sub><b>zhangyaxi</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=xiaomizhou2" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=xiaomizhou2" title="Tests">⚠️</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -542,7 +546,7 @@ Thanks these wonderful people, welcome to join us:
|
||||
|
||||
微信公众号 : 搜索 ID `usthecom`.
|
||||
|
||||
[QQ交流群](https://jq.qq.com/?_wv=1027&k=Bud9OzdI) : 群号 `630061200`
|
||||
[QQ交流群](https://qm.qq.com/q/xxqecSC2cw) : 群号 `1035688434`
|
||||
|
||||
[Github Discussion](https://github.com/apache/hertzbeat/discussions)
|
||||
|
||||
|
||||
+1
-1
@@ -546,7 +546,7 @@ WeChatグループ : `ahertzbeat` を検索.
|
||||
|
||||
WeChat公式アカウント : `usthecom`を検索.
|
||||
|
||||
[QQグループ](https://jq.qq.com/?_wv=1027&k=Bud9OzdI) : グループ番号 `630061200`
|
||||
[QQグループ](https://qm.qq.com/q/xxqecSC2cw) : グループ番号 `1035688434`
|
||||
|
||||
[Github Discussion](https://github.com/apache/hertzbeat/discussions)
|
||||
|
||||
|
||||
+3
@@ -139,6 +139,9 @@ public class RealTimeAlertCalculator {
|
||||
String instanceHost = metricsData.getInstanceHost();
|
||||
String app = metricsData.getApp();
|
||||
String metrics = metricsData.getMetrics();
|
||||
if ((CommonConstants.PROMETHEUS_APP_PREFIX + instanceName).equals(metricsData.getApp())) {
|
||||
app = CommonConstants.PROMETHEUS;
|
||||
}
|
||||
int priority = metricsData.getPriority();
|
||||
int code = metricsData.getCode().getNumber();
|
||||
Map<String, String> labels = metricsData.getLabels();
|
||||
|
||||
+25
-3
@@ -17,17 +17,16 @@
|
||||
|
||||
package org.apache.hertzbeat.alert.controller;
|
||||
|
||||
import static org.apache.hertzbeat.common.constants.CommonConstants.MONITOR_NOT_EXIST_CODE;
|
||||
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import java.util.Objects;
|
||||
import org.apache.hertzbeat.alert.service.AlertDefineService;
|
||||
import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
|
||||
import org.apache.hertzbeat.common.entity.dto.Message;
|
||||
import org.apache.hertzbeat.common.support.exception.AlertExpressionException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
@@ -36,8 +35,17 @@ import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.apache.hertzbeat.common.constants.CommonConstants.FAIL_CODE;
|
||||
import static org.apache.hertzbeat.common.constants.CommonConstants.MONITOR_NOT_EXIST_CODE;
|
||||
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
|
||||
|
||||
/**
|
||||
* Alarm definition management API
|
||||
*/
|
||||
@@ -90,4 +98,18 @@ public class AlertDefineController {
|
||||
return ResponseEntity.ok(Message.success("Delete success"));
|
||||
}
|
||||
|
||||
@GetMapping(path = "/preview/{datasource}")
|
||||
@Operation(summary = "Alarm definition expression preview",
|
||||
description = "If the expression is formal, then the result of the query will be returned, otherwise it will respond with an error")
|
||||
public ResponseEntity<Message<List<Map<String, Object>>>> getDefinePreview(
|
||||
@Parameter(description = "Data Source Type", example = "promql") @PathVariable("datasource") String datasource,
|
||||
@Parameter(description = "alert threshold type:realtime,periodic") @RequestParam String type,
|
||||
@Parameter(description = "alert threshold expression") @RequestParam String expr) {
|
||||
try {
|
||||
return ResponseEntity.ok(Message.successWithData(alertDefineService.getDefinePreview(datasource, type, expr)));
|
||||
} catch (AlertExpressionException ae) {
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(Message.fail(FAIL_CODE, ae.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* 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.dto;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
/**
|
||||
* Alibaba Cloud 'Simple Log Service(SLS)' alert content entity.
|
||||
*
|
||||
* @see <a href="https://help.aliyun.com/zh/sls/user-guide/variables-in-new-alert-templates"/>
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class AlibabaCloudSlsExternAlert {
|
||||
|
||||
/**
|
||||
* The id of the instance on which the alarm was triggered
|
||||
*/
|
||||
@JsonProperty("alert_instance_id")
|
||||
private String alertInstanceId;
|
||||
|
||||
/**
|
||||
* Alarm rule id, unique within project
|
||||
*/
|
||||
@JsonProperty("alert_id")
|
||||
private String alertId;
|
||||
|
||||
/**
|
||||
* Alarm rule name
|
||||
*/
|
||||
@JsonProperty("alert_name")
|
||||
private String alertName;
|
||||
|
||||
/**
|
||||
* Region
|
||||
*/
|
||||
private String region;
|
||||
|
||||
/**
|
||||
* Alarm rule belongs to Project
|
||||
*/
|
||||
private String project;
|
||||
|
||||
/**
|
||||
* Time of this evaluation
|
||||
*/
|
||||
@JsonProperty("alert_time")
|
||||
private int alertTime;
|
||||
|
||||
/**
|
||||
* First trigger time
|
||||
*/
|
||||
@JsonProperty("fire_time")
|
||||
private int fireTime;
|
||||
|
||||
/**
|
||||
* Alarm recovery time
|
||||
* If the alarm status is firing, the value is 0.
|
||||
* If the alarm state is resolved, the value is the specific recovery time.
|
||||
*/
|
||||
@JsonProperty("resolve_time")
|
||||
private int resolveTime;
|
||||
|
||||
/**
|
||||
* Alarm status.
|
||||
* firing: Triggers an alarm.
|
||||
* resolved: Notification of resumption.
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* The total number of entries in the data that triggered the alert,
|
||||
* which may be more than 100, for example after a Cartesian product operation.
|
||||
*/
|
||||
@JsonProperty("fire_results_count")
|
||||
private int fireResultsCount;
|
||||
|
||||
/**
|
||||
* Tag list
|
||||
* Example: {"env":"test"}
|
||||
*/
|
||||
private Map<String, String> labels;
|
||||
|
||||
/**
|
||||
* Labeled lists
|
||||
* Example: { "title": "Alarm title","desc": "Alarm desc" }
|
||||
*/
|
||||
private Map<String, String> annotations;
|
||||
|
||||
/**
|
||||
* Alarm severity.
|
||||
*
|
||||
* 10: Critical
|
||||
* 8: High
|
||||
* 6: Medium
|
||||
* 4: Low
|
||||
* 2: Report only
|
||||
*/
|
||||
private int severity;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@JsonProperty("signin_url")
|
||||
private String signinUrl;
|
||||
|
||||
|
||||
public String getAnnotation(String key) {
|
||||
if (null == this.annotations || this.annotations.isEmpty()) {
|
||||
return "N/A";
|
||||
}
|
||||
return this.annotations.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Severity
|
||||
*/
|
||||
public enum Severity {
|
||||
|
||||
CRITICAL(10, "Critical"),
|
||||
|
||||
HIGH(8, "High"),
|
||||
|
||||
MEDIUM(6, "Medium"),
|
||||
|
||||
LOW(4, "Low"),
|
||||
|
||||
REPORT_ONLY(2, "Report only");
|
||||
|
||||
private static final Map<Integer, Severity> STATUS_MAP;
|
||||
|
||||
static {
|
||||
STATUS_MAP = Arrays.stream(Severity.values()).collect(Collectors.toMap(Severity::getStatus, t -> t, (oldVal, newVal) -> newVal));
|
||||
}
|
||||
|
||||
private final int status;
|
||||
private final String alias;
|
||||
|
||||
Severity(int status, String alias) {
|
||||
this.status = status;
|
||||
this.alias = alias;
|
||||
}
|
||||
|
||||
public static Optional<Severity> convert(int severity) {
|
||||
return Optional.ofNullable(STATUS_MAP.get(severity));
|
||||
}
|
||||
|
||||
public int getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public String getAlias() {
|
||||
return alias;
|
||||
}
|
||||
}
|
||||
}
|
||||
+237
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
* 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.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Huawei Cloud (CES) alert content entity.
|
||||
*
|
||||
* @see <a href="https://support.huaweicloud.com/usermanual-ces/ces_01_0218.html"/>
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class HuaweiCloudExternAlert {
|
||||
|
||||
public static final String FIELD_MESSAGE = "message";
|
||||
public static final String FIELD_MESSAGE_ID = "message_id";
|
||||
public static final String FIELD_TIMESTAMP = "timestamp";
|
||||
public static final String FIELD_TOPIC_URN = "topic_urn";
|
||||
public static final String FIELD_TYPE = "type";
|
||||
public static final String FIELD_SUBJECT = "subject";
|
||||
public static final String FIELD_SUBSCRIBE_URL = "subscribe_url";
|
||||
|
||||
/**
|
||||
* Signature information.
|
||||
*/
|
||||
private String signature;
|
||||
|
||||
/**
|
||||
* Subject
|
||||
*/
|
||||
private String subject;
|
||||
|
||||
/**
|
||||
* The unique identifier of a topic, indicating the topic to which the message belongs.
|
||||
*/
|
||||
@JsonProperty("topic_urn")
|
||||
private String topicUrn;
|
||||
|
||||
/**
|
||||
* Message unique identifier.
|
||||
*/
|
||||
@JsonProperty("message_id")
|
||||
private String messageId;
|
||||
|
||||
/**
|
||||
* Message
|
||||
*/
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* message types, the message types are respectively:
|
||||
* SubscriptionConfirmation、Notification、UnsubscribeConfirmation
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* Subscription confirms the URL that needs to be accessed
|
||||
*/
|
||||
@JsonProperty("subscribe_url")
|
||||
private String subscribeUrl;
|
||||
|
||||
/**
|
||||
* The certificate URL used for message signing, which does not require authentication and can be accessed directly.
|
||||
*/
|
||||
@JsonProperty("signing_cert_url")
|
||||
private String signingCertUrl;
|
||||
|
||||
/**
|
||||
* The timestamp of when the message was first sent.
|
||||
*/
|
||||
private String timestamp;
|
||||
|
||||
/**
|
||||
* Alert message
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public static class AlertMessage {
|
||||
|
||||
private String version;
|
||||
|
||||
private AlertData data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alert data
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public static class AlertData {
|
||||
|
||||
/**
|
||||
* Whether an alarm occurs.
|
||||
* Note: Empty and false are both recovery notifications.
|
||||
* Note: There are no recovery notifications for event types
|
||||
*/
|
||||
@JsonProperty("IsAlarm")
|
||||
private Boolean alarm;
|
||||
|
||||
/**
|
||||
* Alarm time
|
||||
*/
|
||||
@JsonProperty("AlarmTime")
|
||||
private String alarmTime;
|
||||
|
||||
/**
|
||||
* Resource ID
|
||||
*/
|
||||
@JsonProperty("ResourceId")
|
||||
private String resourceId;
|
||||
|
||||
/**
|
||||
* The name of the metric
|
||||
*/
|
||||
@JsonProperty("MetricName")
|
||||
private String metricName;
|
||||
|
||||
/**
|
||||
* Specifies the alarm severity, which can be Critical, Major, Minor, or Informational.
|
||||
*/
|
||||
@JsonProperty("AlarmLevel")
|
||||
private String alarmLevel;
|
||||
|
||||
/**
|
||||
* Namespace
|
||||
*/
|
||||
@JsonProperty("Namespace")
|
||||
private String namespace;
|
||||
|
||||
/**
|
||||
* Region
|
||||
*/
|
||||
@JsonProperty("Region")
|
||||
private String region;
|
||||
|
||||
/**
|
||||
* Dimension name
|
||||
*/
|
||||
@JsonProperty("DimensionName")
|
||||
private String dimensionName;
|
||||
|
||||
/**
|
||||
* Resource name
|
||||
*/
|
||||
@JsonProperty("ResourceName")
|
||||
private String resourceName;
|
||||
|
||||
/**
|
||||
* Alarm record ID
|
||||
*/
|
||||
@JsonProperty("AlarmRecordID")
|
||||
private String alarmRecordId;
|
||||
|
||||
/**
|
||||
* Current data
|
||||
*/
|
||||
@JsonProperty("CurrentData")
|
||||
private String currentData;
|
||||
|
||||
/**
|
||||
* The comparison conditions for the alarm thresholds can be >, =, <, >=, <=.
|
||||
*/
|
||||
@JsonProperty("ComparisonOperator")
|
||||
private String comparisonOperator;
|
||||
|
||||
/**
|
||||
* Alarm value
|
||||
*/
|
||||
@JsonProperty("Value")
|
||||
private String value;
|
||||
|
||||
/**
|
||||
* Number of consecutive occurrences of triggered alarms
|
||||
*/
|
||||
@JsonProperty("Count")
|
||||
private int count;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Huawei cloud alert type
|
||||
*/
|
||||
public enum AlertType {
|
||||
|
||||
SUBSCRIPTION("SubscriptionConfirmation"),
|
||||
|
||||
UNSUBSCRIBE("UnsubscribeConfirmation"),
|
||||
|
||||
NOTIFICATION("Notification");
|
||||
|
||||
private final String type;
|
||||
|
||||
AlertType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public static boolean valid(String type) {
|
||||
if (null == type || type.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return Arrays.stream(AlertType.values()).anyMatch(alertType -> alertType.getType().equals(type));
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* 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.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* volcengine event alert entity class
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class VolcEngineExternEventAlert {
|
||||
@JsonProperty("Type")
|
||||
private String type;
|
||||
|
||||
@JsonProperty("AccountId")
|
||||
private String accountId;
|
||||
|
||||
@JsonProperty("Source")
|
||||
private String source;
|
||||
|
||||
@JsonProperty("ProjectName")
|
||||
private String projectName;
|
||||
|
||||
@JsonProperty("Id")
|
||||
private String id;
|
||||
|
||||
@JsonProperty("EventType")
|
||||
private String eventType;
|
||||
|
||||
@JsonProperty("DescriptionCN")
|
||||
private String descriptionCn;
|
||||
|
||||
@JsonProperty("HappenedAt")
|
||||
private Long happenedAt;
|
||||
|
||||
@JsonProperty("Region")
|
||||
private String region;
|
||||
|
||||
@JsonProperty("Details")
|
||||
private EventDetails details;
|
||||
|
||||
@JsonProperty("Rules")
|
||||
private List<EventRule> rules;
|
||||
|
||||
/**
|
||||
* event details
|
||||
*/
|
||||
@Data
|
||||
public static class EventDetails {
|
||||
@JsonProperty("specversion")
|
||||
private String specVersion;
|
||||
|
||||
@JsonProperty("id")
|
||||
private String id;
|
||||
|
||||
@JsonProperty("source")
|
||||
private String source;
|
||||
|
||||
@JsonProperty("type")
|
||||
private String type;
|
||||
|
||||
@JsonProperty("volcpublishtime")
|
||||
private String volcPublishTime;
|
||||
|
||||
@JsonProperty("volcregion")
|
||||
private String volcRegion;
|
||||
|
||||
@JsonProperty("volcaccountid")
|
||||
private String volcAccountId;
|
||||
|
||||
@JsonProperty("volceventbusname")
|
||||
private String volcEventBusName;
|
||||
|
||||
@JsonProperty("volcresourcename")
|
||||
private String volcResourceName;
|
||||
|
||||
@JsonProperty("subject")
|
||||
private String subject;
|
||||
|
||||
@JsonProperty("time")
|
||||
private String time;
|
||||
}
|
||||
|
||||
/**
|
||||
* event rule
|
||||
*/
|
||||
@Data
|
||||
public static class EventRule {
|
||||
@JsonProperty("RuleId")
|
||||
private String ruleId;
|
||||
|
||||
@JsonProperty("RuleName")
|
||||
private String ruleName;
|
||||
|
||||
@JsonProperty("Level")
|
||||
private String level;
|
||||
}
|
||||
}
|
||||
+203
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
* 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.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Volcengine alarm entity class
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class VolcEngineExternMetricAlert {
|
||||
|
||||
@JsonProperty("Type")
|
||||
private String type;
|
||||
|
||||
@JsonProperty("AccountId")
|
||||
private String accountId;
|
||||
|
||||
@JsonProperty("RuleName")
|
||||
private String ruleName;
|
||||
|
||||
@JsonProperty("RuleId")
|
||||
private String ruleId;
|
||||
|
||||
@JsonProperty("Namespace")
|
||||
private String namespace;
|
||||
|
||||
@JsonProperty("SubNamespace")
|
||||
private String subNamespace;
|
||||
|
||||
@JsonProperty("Level")
|
||||
private String level;
|
||||
|
||||
@JsonProperty("HappenedAt")
|
||||
private String happenedAt;
|
||||
|
||||
@JsonProperty("RuleCondition")
|
||||
private String ruleCondition;
|
||||
|
||||
@JsonProperty("Resources")
|
||||
private List<Resource> resources;
|
||||
|
||||
@JsonProperty("RecoveredResources")
|
||||
private List<Resource> recoveredResources;
|
||||
|
||||
@JsonProperty("NoDataResources")
|
||||
private List<NoDataResource> noDataResources;
|
||||
|
||||
@JsonProperty("NoDataRecoveredResources")
|
||||
private List<NoDataRecoveredResource> noDataRecoveredResources;
|
||||
|
||||
/**
|
||||
* alert resource entity class
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public static class Resource {
|
||||
@JsonProperty("Id")
|
||||
private String id;
|
||||
|
||||
@JsonProperty("Name")
|
||||
private String name;
|
||||
|
||||
@JsonProperty("Region")
|
||||
private String region;
|
||||
|
||||
@JsonProperty("FirstAlertTime")
|
||||
private Long firstAlertTime;
|
||||
|
||||
@JsonProperty("LastAlertTime")
|
||||
private Long lastAlertTime;
|
||||
|
||||
@JsonProperty("Metrics")
|
||||
private List<Metric> metrics;
|
||||
|
||||
@JsonProperty("AlertGroupId")
|
||||
private String alertGroupId;
|
||||
|
||||
@JsonProperty("Dimensions")
|
||||
private List<Dimension> dimensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* alert no data resource entity class
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public static class NoDataResource extends Resource {
|
||||
@JsonProperty("NoDataMetrics")
|
||||
private List<Metric> noDataMetrics;
|
||||
}
|
||||
|
||||
/**
|
||||
* alert no data recovered resource entity class
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public static class NoDataRecoveredResource extends Resource {
|
||||
@JsonProperty("DroppedMetrics")
|
||||
private List<DroppedMetric> droppedMetrics;
|
||||
}
|
||||
|
||||
/**
|
||||
* alert metric entity class
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class Metric {
|
||||
@JsonProperty("Name")
|
||||
private String name;
|
||||
|
||||
@JsonProperty("Unit")
|
||||
private String unit;
|
||||
|
||||
@JsonProperty("Threshold")
|
||||
private Double threshold;
|
||||
|
||||
@JsonProperty("CurrentValue")
|
||||
private Object currentValue;
|
||||
|
||||
@JsonProperty("Description")
|
||||
private String description;
|
||||
|
||||
@JsonProperty("Warning")
|
||||
private Boolean warning;
|
||||
}
|
||||
|
||||
/**
|
||||
* alert dropped metric entity class
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public static class DroppedMetric extends Metric {
|
||||
@JsonProperty("Reason")
|
||||
private String reason;
|
||||
}
|
||||
|
||||
/**
|
||||
* resource dimension entity class
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class Dimension {
|
||||
@JsonProperty("Name")
|
||||
private String name;
|
||||
|
||||
@JsonProperty("NameCN")
|
||||
private String nameCn;
|
||||
|
||||
@JsonProperty("Value")
|
||||
private String value;
|
||||
|
||||
@JsonProperty("Description")
|
||||
private String description;
|
||||
}
|
||||
|
||||
|
||||
public static final String ALERT_TYPE_METRIC = "Metric";
|
||||
public static final String ALERT_TYPE_EVENT = "Event";
|
||||
public static final String ALERT_TYPE_METRIC_RECOVERED = "MetricRecovered";
|
||||
public static final String ALERT_TYPE_METRICS_NODATA = "MetricsNoData";
|
||||
public static final String ALERT_TYPE_NO_DATA_RECOVERED = "NoDataRecovered";
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
+250
-10
@@ -47,14 +47,6 @@ public class AlertExpressionBaseVisitor<T> extends AbstractParseTreeVisitor<T> i
|
||||
*/
|
||||
@Override public T visitAndExpr(AlertExpressionParser.AndExprContext ctx) { return visitChildren(ctx); }
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitQueryExpr(AlertExpressionParser.QueryExprContext ctx) { return visitChildren(ctx); }
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
@@ -71,6 +63,22 @@ public class AlertExpressionBaseVisitor<T> extends AbstractParseTreeVisitor<T> i
|
||||
*/
|
||||
@Override public T visitUnlessExpr(AlertExpressionParser.UnlessExprContext ctx) { return visitChildren(ctx); }
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitSqlExpr(AlertExpressionParser.SqlExprContext ctx) { return visitChildren(ctx); }
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitSqlCallExpr(AlertExpressionParser.SqlCallExprContext ctx) { return visitChildren(ctx); }
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
@@ -88,7 +96,23 @@ public class AlertExpressionBaseVisitor<T> extends AbstractParseTreeVisitor<T> i
|
||||
@Override public T visitParenExpr(AlertExpressionParser.ParenExprContext ctx) { return visitChildren(ctx); }
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitPromqlCallExpr(AlertExpressionParser.PromqlCallExprContext ctx) { return visitChildren(ctx); }
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitPromqlExpr(AlertExpressionParser.PromqlExprContext ctx) { return visitChildren(ctx); }
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
@@ -101,7 +125,23 @@ public class AlertExpressionBaseVisitor<T> extends AbstractParseTreeVisitor<T> i
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitIdentifier(AlertExpressionParser.IdentifierContext ctx) { return visitChildren(ctx); }
|
||||
@Override public T visitFunctionCall(AlertExpressionParser.FunctionCallContext ctx) { return visitChildren(ctx); }
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitParameterList(AlertExpressionParser.ParameterListContext ctx) { return visitChildren(ctx); }
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitParameter(AlertExpressionParser.ParameterContext ctx) { return visitChildren(ctx); }
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
@@ -110,4 +150,204 @@ public class AlertExpressionBaseVisitor<T> extends AbstractParseTreeVisitor<T> i
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitNumber(AlertExpressionParser.NumberContext ctx) { return visitChildren(ctx); }
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitString(AlertExpressionParser.StringContext ctx) { return visitChildren(ctx); }
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitDuration(AlertExpressionParser.DurationContext ctx) { return visitChildren(ctx); }
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitFunctionName(AlertExpressionParser.FunctionNameContext ctx) { return visitChildren(ctx); }
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitSelectSql(AlertExpressionParser.SelectSqlContext ctx) { return visitChildren(ctx); }
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitSelectFieldList(AlertExpressionParser.SelectFieldListContext ctx) { return visitChildren(ctx); }
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitSelectField(AlertExpressionParser.SelectFieldContext ctx) { return visitChildren(ctx); }
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitGroupByList(AlertExpressionParser.GroupByListContext ctx) { return visitChildren(ctx); }
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitOrderByList(AlertExpressionParser.OrderByListContext ctx) { return visitChildren(ctx); }
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitOrderByField(AlertExpressionParser.OrderByFieldContext ctx) { return visitChildren(ctx); }
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitLimitClause(AlertExpressionParser.LimitClauseContext ctx) { return visitChildren(ctx); }
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitRelList(AlertExpressionParser.RelListContext ctx) { return visitChildren(ctx); }
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitRelation(AlertExpressionParser.RelationContext ctx) { return visitChildren(ctx); }
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitConditionList(AlertExpressionParser.ConditionListContext ctx) { return visitChildren(ctx); }
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitCompOp(AlertExpressionParser.CompOpContext ctx) { return visitChildren(ctx); }
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitCondition(AlertExpressionParser.ConditionContext ctx) { return visitChildren(ctx); }
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitConditionUnit(AlertExpressionParser.ConditionUnitContext ctx) { return visitChildren(ctx); }
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitPromql(AlertExpressionParser.PromqlContext ctx) { return visitChildren(ctx); }
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitMetricSelector(AlertExpressionParser.MetricSelectorContext ctx) { return visitChildren(ctx); }
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitLabelMatcherList(AlertExpressionParser.LabelMatcherListContext ctx) { return visitChildren(ctx); }
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitLabelMatcherItem(AlertExpressionParser.LabelMatcherItemContext ctx) { return visitChildren(ctx); }
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitLabelMatcherOp(AlertExpressionParser.LabelMatcherOpContext ctx) { return visitChildren(ctx); }
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitLabelList(AlertExpressionParser.LabelListContext ctx) { return visitChildren(ctx); }
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitInstantVectorOp(AlertExpressionParser.InstantVectorOpContext ctx) { return visitChildren(ctx); }
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitAggregationOperator(AlertExpressionParser.AggregationOperatorContext ctx) { return visitChildren(ctx); }
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitBinaryOperator(AlertExpressionParser.BinaryOperatorContext ctx) { return visitChildren(ctx); }
|
||||
}
|
||||
+106
-118
@@ -17,6 +17,7 @@
|
||||
|
||||
package org.apache.hertzbeat.alert.expr;
|
||||
|
||||
import org.antlr.v4.runtime.CommonTokenStream;
|
||||
import org.apache.hertzbeat.warehouse.db.QueryExecutor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -24,6 +25,8 @@ import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Alert expression visitor implement
|
||||
@@ -31,12 +34,16 @@ import java.util.Map;
|
||||
public class AlertExpressionEvalVisitor extends AlertExpressionBaseVisitor<List<Map<String, Object>>> {
|
||||
|
||||
private static final String THRESHOLD = "__threshold__";
|
||||
private static final String NAME = "__name__";
|
||||
private static final String VALUE = "__value__";
|
||||
private static final String TIMESTAMP = "__timestamp__";
|
||||
|
||||
private final QueryExecutor executor;
|
||||
private final CommonTokenStream tokens;
|
||||
|
||||
public AlertExpressionEvalVisitor(QueryExecutor executor) {
|
||||
public AlertExpressionEvalVisitor(QueryExecutor executor, CommonTokenStream tokens) {
|
||||
this.executor = executor;
|
||||
this.tokens = tokens;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -66,10 +73,11 @@ public class AlertExpressionEvalVisitor extends AlertExpressionBaseVisitor<List<
|
||||
}
|
||||
// queryValues may be a list of values, or a single value
|
||||
Object matchValue = evaluateCondition(queryValues, operator, threshold);
|
||||
item.put(VALUE, matchValue);
|
||||
Map<String, Object> resultMap = new HashMap(item);
|
||||
resultMap.put(VALUE, matchValue);
|
||||
// if matchValue is null, mean not match the threshold
|
||||
// if not null, mean match the threshold
|
||||
result.add(new HashMap<>(item));
|
||||
result.add(resultMap);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -80,42 +88,26 @@ public class AlertExpressionEvalVisitor extends AlertExpressionBaseVisitor<List<
|
||||
public List<Map<String, Object>> visitAndExpr(AlertExpressionParser.AndExprContext ctx) {
|
||||
List<Map<String, Object>> leftOperand = visit(ctx.left);
|
||||
List<Map<String, Object>> rightOperand = visit(ctx.right);
|
||||
List<Map<String, Object>> results = new ArrayList<>();
|
||||
|
||||
Map<String, Object> leftMap = null;
|
||||
boolean leftMatch = false;
|
||||
Map<String, Object> rightMap = null;
|
||||
boolean rightMatch = false;
|
||||
for (Map<String, Object> item : leftOperand) {
|
||||
if (leftMap == null) {
|
||||
leftMap = item;
|
||||
// build a hash set of the right-side tag collection
|
||||
Set<String> rightLabelsSet = rightOperand.stream()
|
||||
.filter(item -> item.get(VALUE) != null)
|
||||
.map(this::labelKey)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
// iterate over the left side, O(1) match
|
||||
for (Map<String, Object> leftItem : leftOperand) {
|
||||
Object leftVal = leftItem.get(VALUE);
|
||||
if (leftVal == null) {
|
||||
continue;
|
||||
}
|
||||
if (item.get(VALUE) != null) {
|
||||
leftMap = item;
|
||||
leftMatch = true;
|
||||
break;
|
||||
String labelKey = labelKey(leftItem);
|
||||
if (rightLabelsSet.contains(labelKey)) {
|
||||
results.add(new HashMap<>(leftItem));
|
||||
}
|
||||
}
|
||||
for (Map<String, Object> item : rightOperand) {
|
||||
if (rightMap == null) {
|
||||
rightMap = item;
|
||||
}
|
||||
if (item.get(VALUE) != null) {
|
||||
rightMap = item;
|
||||
rightMatch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (leftMatch && rightMatch) {
|
||||
rightMap.putAll(leftMap);
|
||||
return new LinkedList<>(List.of(rightMap));
|
||||
} else if (leftMap != null) {
|
||||
leftMap.put(VALUE, null);
|
||||
return new LinkedList<>(List.of(leftMap));
|
||||
} else if (rightMap != null) {
|
||||
rightMap.put(VALUE, null);
|
||||
return new LinkedList<>(List.of(rightMap));
|
||||
}
|
||||
return new LinkedList<>();
|
||||
return results;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -123,98 +115,51 @@ public class AlertExpressionEvalVisitor extends AlertExpressionBaseVisitor<List<
|
||||
List<Map<String, Object>> leftOperand = visit(ctx.left);
|
||||
List<Map<String, Object>> rightOperand = visit(ctx.right);
|
||||
|
||||
Map<String, Object> leftMap = null;
|
||||
boolean leftMatch = false;
|
||||
Map<String, Object> rightMap = null;
|
||||
boolean rightMatch = false;
|
||||
for (Map<String, Object> item : leftOperand) {
|
||||
if (leftMap == null) {
|
||||
leftMap = item;
|
||||
// build a hashMap of the left-hand label collection
|
||||
Map<String, Map<String, Object>> leftLabelMap = leftOperand.stream()
|
||||
.filter(item -> item.get(VALUE) != null)
|
||||
.collect(Collectors.toMap(this::labelKey, HashMap::new, (k1, k2) -> k1));
|
||||
|
||||
// first add all the non-empty items on the left side
|
||||
List<Map<String, Object>> results = new ArrayList<>(leftLabelMap.values());
|
||||
|
||||
// add the term that has a value on the right side and not on the left side
|
||||
for (Map<String, Object> rightItem : rightOperand) {
|
||||
Object rightVal = rightItem.get(VALUE);
|
||||
if (rightVal == null) {
|
||||
continue;
|
||||
}
|
||||
if (item.get(VALUE) != null) {
|
||||
leftMap = item;
|
||||
leftMatch = true;
|
||||
break;
|
||||
String key = labelKey(rightItem);
|
||||
if (!leftLabelMap.containsKey(key)) {
|
||||
results.add(new HashMap<>(rightItem));
|
||||
}
|
||||
}
|
||||
for (Map<String, Object> item : rightOperand) {
|
||||
if (rightMap == null) {
|
||||
rightMap = item;
|
||||
}
|
||||
if (item.get(VALUE) != null) {
|
||||
rightMap = item;
|
||||
rightMatch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (leftMatch && rightMatch) {
|
||||
rightMap.putAll(leftMap);
|
||||
return new LinkedList<>(List.of(rightMap));
|
||||
} else if (leftMatch) {
|
||||
return new LinkedList<>(List.of(leftMap));
|
||||
} else if (rightMatch) {
|
||||
return new LinkedList<>(List.of(rightMap));
|
||||
} else {
|
||||
if (leftMap != null && rightMap != null) {
|
||||
rightMap.putAll(leftMap);
|
||||
return new LinkedList<>(List.of(rightMap));
|
||||
} else if (leftMap != null) {
|
||||
return new LinkedList<>(List.of(leftMap));
|
||||
} else if (rightMap != null) {
|
||||
return new LinkedList<>(List.of(rightMap));
|
||||
}
|
||||
}
|
||||
return new LinkedList<>();
|
||||
return results;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Map<String, Object>> visitUnlessExpr(AlertExpressionParser.UnlessExprContext ctx) {
|
||||
List<Map<String, Object>> leftOperand = visit(ctx.left);
|
||||
List<Map<String, Object>> rightOperand = visit(ctx.right);
|
||||
Map<String, Object> leftMap = null;
|
||||
boolean leftMatch = false;
|
||||
Map<String, Object> rightMap = null;
|
||||
boolean rightMatch = false;
|
||||
for (Map<String, Object> item : leftOperand) {
|
||||
if (leftMap == null) {
|
||||
leftMap = item;
|
||||
}
|
||||
if (item.get(VALUE) != null) {
|
||||
leftMap = item;
|
||||
leftMatch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (Map<String, Object> item : rightOperand) {
|
||||
if (rightMap == null) {
|
||||
rightMap = item;
|
||||
}
|
||||
if (item.get(VALUE) != null) {
|
||||
rightMap = item;
|
||||
rightMatch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (leftMatch && !rightMatch) {
|
||||
return new LinkedList<>(List.of(leftMap));
|
||||
} else {
|
||||
if (leftMap != null) {
|
||||
leftMap.put(VALUE, null);
|
||||
return new LinkedList<>(List.of(leftMap));
|
||||
} else {
|
||||
if (rightMap != null) {
|
||||
rightMap.put(VALUE, null);
|
||||
return new LinkedList<>(List.of(rightMap));
|
||||
}
|
||||
}
|
||||
}
|
||||
return new LinkedList<>();
|
||||
}
|
||||
List<Map<String, Object>> results = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public List<Map<String, Object>> visitQueryExpr(AlertExpressionParser.QueryExprContext ctx) {
|
||||
String query = ctx.identifier().getText();
|
||||
return executor.execute(query);
|
||||
// build a hash set of the right-side tag collection
|
||||
Set<String> rightLabelSet = rightOperand.stream()
|
||||
.filter(item -> item.get(VALUE) != null)
|
||||
.map(this::labelKey)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
// iterate over the left side, O(1) match
|
||||
for (Map<String, Object> leftItem : leftOperand) {
|
||||
Object leftVal = leftItem.get(VALUE);
|
||||
if (leftVal == null) {
|
||||
continue;
|
||||
}
|
||||
if (!rightLabelSet.contains(labelKey(leftItem))) {
|
||||
results.add(new HashMap<>(leftItem));
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -227,6 +172,28 @@ public class AlertExpressionEvalVisitor extends AlertExpressionBaseVisitor<List<
|
||||
return numAsList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Map<String, Object>> visitPromqlExpr(AlertExpressionParser.PromqlExprContext ctx) {
|
||||
String rawPromql = tokens.getText(ctx.promql());
|
||||
return executor.execute(rawPromql);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Map<String, Object>> visitSqlExpr(AlertExpressionParser.SqlExprContext ctx) {
|
||||
String rawSql = tokens.getText(ctx.selectSql());
|
||||
return executor.execute(rawSql);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Map<String, Object>> visitSqlCallExpr(AlertExpressionParser.SqlCallExprContext ctx) {
|
||||
return callSqlOrPromql(tokens.getText(ctx.string()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Map<String, Object>> visitPromqlCallExpr(AlertExpressionParser.PromqlCallExprContext ctx) {
|
||||
return callSqlOrPromql(tokens.getText(ctx.string()));
|
||||
}
|
||||
|
||||
private Object evaluateCondition(Object value, String operator, Double threshold) {
|
||||
// value may be a list of values, or a single value
|
||||
switch (operator) {
|
||||
@@ -302,4 +269,25 @@ public class AlertExpressionEvalVisitor extends AlertExpressionBaseVisitor<List<
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<Map<String, Object>> callSqlOrPromql(String text){
|
||||
String script = text.substring(1, text.length() - 1);
|
||||
return executor.execute(script);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate tag key (excluding `__name__` and `__value__` and `__timestamp__`)
|
||||
*/
|
||||
private String labelKey(Map<String, Object> labelsMap) {
|
||||
if (null == labelsMap || labelsMap.isEmpty()) {
|
||||
return "-";
|
||||
}
|
||||
String key = labelsMap.entrySet().stream()
|
||||
.filter(e -> !e.getKey().equals(VALUE) && !e.getKey().equals(NAME) && !e.getKey().equals(TIMESTAMP))
|
||||
.sorted(Map.Entry.comparingByKey())
|
||||
.map(e -> e.getKey() + "=" + (e.getValue() == null ? "" : e.getValue()))
|
||||
.collect(Collectors.joining(","));
|
||||
return key.isEmpty() ? "-" : key;
|
||||
}
|
||||
|
||||
}
|
||||
+396
-66
@@ -36,37 +36,66 @@ public class AlertExpressionLexer extends Lexer {
|
||||
|
||||
protected static final DFA[] _decisionToDFA;
|
||||
protected static final PredictionContextCache _sharedContextCache =
|
||||
new PredictionContextCache();
|
||||
new PredictionContextCache();
|
||||
public static final int
|
||||
AND=1, OR=2, UNLESS=3, GT=4, GE=5, LT=6, LE=7, EQ=8, NE=9, LPAREN=10,
|
||||
RPAREN=11, IDENTIFIER=12, NUMBER=13, WS=14;
|
||||
AND=1, OR=2, UNLESS=3, NOT=4, SELECT=5, FROM=6, WHERE=7, GROUP=8, BY=9,
|
||||
HAVING=10, ORDER=11, LIMIT=12, OFFSET=13, AS=14, ASC=15, DESC=16, IN=17,
|
||||
IS=18, NULL=19, LIKE=20, BETWEEN=21, STAR=22, COUNT=23, SUM=24, AVG=25,
|
||||
MIN=26, MAX=27, STDDEV=28, STDVAR=29, VARIANCE=30, RATE_FUNCTION=31, INCREASE_FUNCTION=32,
|
||||
HISTOGRAM_QUANTILE_FUNCTION=33, TOPK=34, BOTTOMK=35, QUANTILE=36, BY_FUNCTION=37,
|
||||
WITHOUT_FUNCTION=38, GROUP_LEFT_FUNCTION=39, GROUP_RIGHT_FUNCTION=40,
|
||||
IGNORING_FUNCTION=41, ON_FUNCTION=42, SQL_FUNCTION=43, PROMQL_FUNCTION=44,
|
||||
GT=45, GE=46, LT=47, LE=48, EQ=49, NE=50, LPAREN=51, RPAREN=52, LBRACE=53,
|
||||
RBRACE=54, LBRACKET=55, RBRACKET=56, COMMA=57, DOT=58, COLON=59, SEMICOLON=60,
|
||||
SCIENTIFIC_NUMBER=61, FLOAT=62, NUMBER=63, DURATION=64, STRING=65, IDENTIFIER=66,
|
||||
WS=67, LINE_COMMENT=68, BLOCK_COMMENT=69;
|
||||
public static String[] channelNames = {
|
||||
"DEFAULT_TOKEN_CHANNEL", "HIDDEN"
|
||||
"DEFAULT_TOKEN_CHANNEL", "HIDDEN"
|
||||
};
|
||||
|
||||
public static String[] modeNames = {
|
||||
"DEFAULT_MODE"
|
||||
"DEFAULT_MODE"
|
||||
};
|
||||
|
||||
private static String[] makeRuleNames() {
|
||||
return new String[] {
|
||||
"AND", "OR", "UNLESS", "GT", "GE", "LT", "LE", "EQ", "NE", "LPAREN",
|
||||
"RPAREN", "IDENTIFIER", "NUMBER", "WS"
|
||||
"AND", "OR", "UNLESS", "NOT", "SELECT", "FROM", "WHERE", "GROUP", "BY",
|
||||
"HAVING", "ORDER", "LIMIT", "OFFSET", "AS", "ASC", "DESC", "IN", "IS",
|
||||
"NULL", "LIKE", "BETWEEN", "STAR", "COUNT", "SUM", "AVG", "MIN", "MAX",
|
||||
"STDDEV", "STDVAR", "VARIANCE", "RATE_FUNCTION", "INCREASE_FUNCTION",
|
||||
"HISTOGRAM_QUANTILE_FUNCTION", "TOPK", "BOTTOMK", "QUANTILE", "BY_FUNCTION",
|
||||
"WITHOUT_FUNCTION", "GROUP_LEFT_FUNCTION", "GROUP_RIGHT_FUNCTION", "IGNORING_FUNCTION",
|
||||
"ON_FUNCTION", "SQL_FUNCTION", "PROMQL_FUNCTION", "GT", "GE", "LT", "LE",
|
||||
"EQ", "NE", "LPAREN", "RPAREN", "LBRACE", "RBRACE", "LBRACKET", "RBRACKET",
|
||||
"COMMA", "DOT", "COLON", "SEMICOLON", "SCIENTIFIC_NUMBER", "FLOAT", "NUMBER",
|
||||
"DURATION", "STRING", "IDENTIFIER", "WS", "LINE_COMMENT", "BLOCK_COMMENT"
|
||||
};
|
||||
}
|
||||
public static final String[] ruleNames = makeRuleNames();
|
||||
|
||||
private static String[] makeLiteralNames() {
|
||||
return new String[] {
|
||||
null, "'and'", "'or'", "'unless'", "'>'", "'>='", "'<'", "'<='", "'=='",
|
||||
"'!='", "'('", "')'"
|
||||
null, null, null, null, null, null, null, null, null, null, null, null,
|
||||
null, null, null, null, null, null, null, null, null, null, "'*'", null,
|
||||
null, null, null, null, null, null, null, null, null, null, null, null,
|
||||
null, null, null, null, null, null, null, null, null, "'>'", "'>='",
|
||||
"'<'", "'<='", null, "'!='", "'('", "')'", "'{'", "'}'", "'['", "']'",
|
||||
"','", "'.'", "':'", "';'"
|
||||
};
|
||||
}
|
||||
private static final String[] _LITERAL_NAMES = makeLiteralNames();
|
||||
private static String[] makeSymbolicNames() {
|
||||
return new String[] {
|
||||
null, "AND", "OR", "UNLESS", "GT", "GE", "LT", "LE", "EQ", "NE", "LPAREN",
|
||||
"RPAREN", "IDENTIFIER", "NUMBER", "WS"
|
||||
null, "AND", "OR", "UNLESS", "NOT", "SELECT", "FROM", "WHERE", "GROUP",
|
||||
"BY", "HAVING", "ORDER", "LIMIT", "OFFSET", "AS", "ASC", "DESC", "IN",
|
||||
"IS", "NULL", "LIKE", "BETWEEN", "STAR", "COUNT", "SUM", "AVG", "MIN",
|
||||
"MAX", "STDDEV", "STDVAR", "VARIANCE", "RATE_FUNCTION", "INCREASE_FUNCTION",
|
||||
"HISTOGRAM_QUANTILE_FUNCTION", "TOPK", "BOTTOMK", "QUANTILE", "BY_FUNCTION",
|
||||
"WITHOUT_FUNCTION", "GROUP_LEFT_FUNCTION", "GROUP_RIGHT_FUNCTION", "IGNORING_FUNCTION",
|
||||
"ON_FUNCTION", "SQL_FUNCTION", "PROMQL_FUNCTION", "GT", "GE", "LT", "LE",
|
||||
"EQ", "NE", "LPAREN", "RPAREN", "LBRACE", "RBRACE", "LBRACKET", "RBRACKET",
|
||||
"COMMA", "DOT", "COLON", "SEMICOLON", "SCIENTIFIC_NUMBER", "FLOAT", "NUMBER",
|
||||
"DURATION", "STRING", "IDENTIFIER", "WS", "LINE_COMMENT", "BLOCK_COMMENT"
|
||||
};
|
||||
}
|
||||
private static final String[] _SYMBOLIC_NAMES = makeSymbolicNames();
|
||||
@@ -128,62 +157,363 @@ public class AlertExpressionLexer extends Lexer {
|
||||
public ATN getATN() { return _ATN; }
|
||||
|
||||
public static final String _serializedATN =
|
||||
"\u0004\u0000\u000eZ\u0006\uffff\uffff\u0002\u0000\u0007\u0000\u0002\u0001"+
|
||||
"\u0007\u0001\u0002\u0002\u0007\u0002\u0002\u0003\u0007\u0003\u0002\u0004"+
|
||||
"\u0007\u0004\u0002\u0005\u0007\u0005\u0002\u0006\u0007\u0006\u0002\u0007"+
|
||||
"\u0007\u0007\u0002\b\u0007\b\u0002\t\u0007\t\u0002\n\u0007\n\u0002\u000b"+
|
||||
"\u0007\u000b\u0002\f\u0007\f\u0002\r\u0007\r\u0001\u0000\u0001\u0000\u0001"+
|
||||
"\u0000\u0001\u0000\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0002\u0001"+
|
||||
"\u0002\u0001\u0002\u0001\u0002\u0001\u0002\u0001\u0002\u0001\u0002\u0001"+
|
||||
"\u0003\u0001\u0003\u0001\u0004\u0001\u0004\u0001\u0004\u0001\u0005\u0001"+
|
||||
"\u0005\u0001\u0006\u0001\u0006\u0001\u0006\u0001\u0007\u0001\u0007\u0001"+
|
||||
"\u0007\u0001\b\u0001\b\u0001\b\u0001\t\u0001\t\u0001\n\u0001\n\u0001\u000b"+
|
||||
"\u0001\u000b\u0005\u000bB\b\u000b\n\u000b\f\u000bE\t\u000b\u0001\f\u0004"+
|
||||
"\fH\b\f\u000b\f\f\fI\u0001\f\u0001\f\u0004\fN\b\f\u000b\f\f\fO\u0003\f"+
|
||||
"R\b\f\u0001\r\u0004\rU\b\r\u000b\r\f\rV\u0001\r\u0001\r\u0000\u0000\u000e"+
|
||||
"\u0001\u0001\u0003\u0002\u0005\u0003\u0007\u0004\t\u0005\u000b\u0006\r"+
|
||||
"\u0007\u000f\b\u0011\t\u0013\n\u0015\u000b\u0017\f\u0019\r\u001b\u000e"+
|
||||
"\u0001\u0000\u0004\u0003\u0000AZ__az\n\u0000\"\"..09==A[]]__a{}}\u8000"+
|
||||
"\uff5e\u8000\uff5e\u0001\u000009\u0003\u0000\t\n\r\r ^\u0000\u0001\u0001"+
|
||||
"\u0000\u0000\u0000\u0000\u0003\u0001\u0000\u0000\u0000\u0000\u0005\u0001"+
|
||||
"\u0000\u0000\u0000\u0000\u0007\u0001\u0000\u0000\u0000\u0000\t\u0001\u0000"+
|
||||
"\u0000\u0000\u0000\u000b\u0001\u0000\u0000\u0000\u0000\r\u0001\u0000\u0000"+
|
||||
"\u0000\u0000\u000f\u0001\u0000\u0000\u0000\u0000\u0011\u0001\u0000\u0000"+
|
||||
"\u0000\u0000\u0013\u0001\u0000\u0000\u0000\u0000\u0015\u0001\u0000\u0000"+
|
||||
"\u0000\u0000\u0017\u0001\u0000\u0000\u0000\u0000\u0019\u0001\u0000\u0000"+
|
||||
"\u0000\u0000\u001b\u0001\u0000\u0000\u0000\u0001\u001d\u0001\u0000\u0000"+
|
||||
"\u0000\u0003!\u0001\u0000\u0000\u0000\u0005$\u0001\u0000\u0000\u0000\u0007"+
|
||||
"+\u0001\u0000\u0000\u0000\t-\u0001\u0000\u0000\u0000\u000b0\u0001\u0000"+
|
||||
"\u0000\u0000\r2\u0001\u0000\u0000\u0000\u000f5\u0001\u0000\u0000\u0000"+
|
||||
"\u00118\u0001\u0000\u0000\u0000\u0013;\u0001\u0000\u0000\u0000\u0015="+
|
||||
"\u0001\u0000\u0000\u0000\u0017?\u0001\u0000\u0000\u0000\u0019G\u0001\u0000"+
|
||||
"\u0000\u0000\u001bT\u0001\u0000\u0000\u0000\u001d\u001e\u0005a\u0000\u0000"+
|
||||
"\u001e\u001f\u0005n\u0000\u0000\u001f \u0005d\u0000\u0000 \u0002\u0001"+
|
||||
"\u0000\u0000\u0000!\"\u0005o\u0000\u0000\"#\u0005r\u0000\u0000#\u0004"+
|
||||
"\u0001\u0000\u0000\u0000$%\u0005u\u0000\u0000%&\u0005n\u0000\u0000&\'"+
|
||||
"\u0005l\u0000\u0000\'(\u0005e\u0000\u0000()\u0005s\u0000\u0000)*\u0005"+
|
||||
"s\u0000\u0000*\u0006\u0001\u0000\u0000\u0000+,\u0005>\u0000\u0000,\b\u0001"+
|
||||
"\u0000\u0000\u0000-.\u0005>\u0000\u0000./\u0005=\u0000\u0000/\n\u0001"+
|
||||
"\u0000\u0000\u000001\u0005<\u0000\u00001\f\u0001\u0000\u0000\u000023\u0005"+
|
||||
"<\u0000\u000034\u0005=\u0000\u00004\u000e\u0001\u0000\u0000\u000056\u0005"+
|
||||
"=\u0000\u000067\u0005=\u0000\u00007\u0010\u0001\u0000\u0000\u000089\u0005"+
|
||||
"!\u0000\u00009:\u0005=\u0000\u0000:\u0012\u0001\u0000\u0000\u0000;<\u0005"+
|
||||
"(\u0000\u0000<\u0014\u0001\u0000\u0000\u0000=>\u0005)\u0000\u0000>\u0016"+
|
||||
"\u0001\u0000\u0000\u0000?C\u0007\u0000\u0000\u0000@B\u0007\u0001\u0000"+
|
||||
"\u0000A@\u0001\u0000\u0000\u0000BE\u0001\u0000\u0000\u0000CA\u0001\u0000"+
|
||||
"\u0000\u0000CD\u0001\u0000\u0000\u0000D\u0018\u0001\u0000\u0000\u0000"+
|
||||
"EC\u0001\u0000\u0000\u0000FH\u0007\u0002\u0000\u0000GF\u0001\u0000\u0000"+
|
||||
"\u0000HI\u0001\u0000\u0000\u0000IG\u0001\u0000\u0000\u0000IJ\u0001\u0000"+
|
||||
"\u0000\u0000JQ\u0001\u0000\u0000\u0000KM\u0005.\u0000\u0000LN\u0007\u0002"+
|
||||
"\u0000\u0000ML\u0001\u0000\u0000\u0000NO\u0001\u0000\u0000\u0000OM\u0001"+
|
||||
"\u0000\u0000\u0000OP\u0001\u0000\u0000\u0000PR\u0001\u0000\u0000\u0000"+
|
||||
"QK\u0001\u0000\u0000\u0000QR\u0001\u0000\u0000\u0000R\u001a\u0001\u0000"+
|
||||
"\u0000\u0000SU\u0007\u0003\u0000\u0000TS\u0001\u0000\u0000\u0000UV\u0001"+
|
||||
"\u0000\u0000\u0000VT\u0001\u0000\u0000\u0000VW\u0001\u0000\u0000\u0000"+
|
||||
"WX\u0001\u0000\u0000\u0000XY\u0006\r\u0000\u0000Y\u001c\u0001\u0000\u0000"+
|
||||
"\u0000\u0006\u0000CIOQV\u0001\u0006\u0000\u0000";
|
||||
"\u0004\u0000E\u0228\u0006\uffff\uffff\u0002\u0000\u0007\u0000\u0002\u0001"+
|
||||
"\u0007\u0001\u0002\u0002\u0007\u0002\u0002\u0003\u0007\u0003\u0002\u0004"+
|
||||
"\u0007\u0004\u0002\u0005\u0007\u0005\u0002\u0006\u0007\u0006\u0002\u0007"+
|
||||
"\u0007\u0007\u0002\b\u0007\b\u0002\t\u0007\t\u0002\n\u0007\n\u0002\u000b"+
|
||||
"\u0007\u000b\u0002\f\u0007\f\u0002\r\u0007\r\u0002\u000e\u0007\u000e\u0002"+
|
||||
"\u000f\u0007\u000f\u0002\u0010\u0007\u0010\u0002\u0011\u0007\u0011\u0002"+
|
||||
"\u0012\u0007\u0012\u0002\u0013\u0007\u0013\u0002\u0014\u0007\u0014\u0002"+
|
||||
"\u0015\u0007\u0015\u0002\u0016\u0007\u0016\u0002\u0017\u0007\u0017\u0002"+
|
||||
"\u0018\u0007\u0018\u0002\u0019\u0007\u0019\u0002\u001a\u0007\u001a\u0002"+
|
||||
"\u001b\u0007\u001b\u0002\u001c\u0007\u001c\u0002\u001d\u0007\u001d\u0002"+
|
||||
"\u001e\u0007\u001e\u0002\u001f\u0007\u001f\u0002 \u0007 \u0002!\u0007"+
|
||||
"!\u0002\"\u0007\"\u0002#\u0007#\u0002$\u0007$\u0002%\u0007%\u0002&\u0007"+
|
||||
"&\u0002\'\u0007\'\u0002(\u0007(\u0002)\u0007)\u0002*\u0007*\u0002+\u0007"+
|
||||
"+\u0002,\u0007,\u0002-\u0007-\u0002.\u0007.\u0002/\u0007/\u00020\u0007"+
|
||||
"0\u00021\u00071\u00022\u00072\u00023\u00073\u00024\u00074\u00025\u0007"+
|
||||
"5\u00026\u00076\u00027\u00077\u00028\u00078\u00029\u00079\u0002:\u0007"+
|
||||
":\u0002;\u0007;\u0002<\u0007<\u0002=\u0007=\u0002>\u0007>\u0002?\u0007"+
|
||||
"?\u0002@\u0007@\u0002A\u0007A\u0002B\u0007B\u0002C\u0007C\u0002D\u0007"+
|
||||
"D\u0001\u0000\u0001\u0000\u0001\u0000\u0001\u0000\u0001\u0001\u0001\u0001"+
|
||||
"\u0001\u0001\u0001\u0002\u0001\u0002\u0001\u0002\u0001\u0002\u0001\u0002"+
|
||||
"\u0001\u0002\u0001\u0002\u0001\u0003\u0001\u0003\u0001\u0003\u0001\u0003"+
|
||||
"\u0001\u0004\u0001\u0004\u0001\u0004\u0001\u0004\u0001\u0004\u0001\u0004"+
|
||||
"\u0001\u0004\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005"+
|
||||
"\u0001\u0006\u0001\u0006\u0001\u0006\u0001\u0006\u0001\u0006\u0001\u0006"+
|
||||
"\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007"+
|
||||
"\u0001\b\u0001\b\u0001\b\u0001\t\u0001\t\u0001\t\u0001\t\u0001\t\u0001"+
|
||||
"\t\u0001\t\u0001\n\u0001\n\u0001\n\u0001\n\u0001\n\u0001\n\u0001\u000b"+
|
||||
"\u0001\u000b\u0001\u000b\u0001\u000b\u0001\u000b\u0001\u000b\u0001\f\u0001"+
|
||||
"\f\u0001\f\u0001\f\u0001\f\u0001\f\u0001\f\u0001\r\u0001\r\u0001\r\u0001"+
|
||||
"\u000e\u0001\u000e\u0001\u000e\u0001\u000e\u0001\u000f\u0001\u000f\u0001"+
|
||||
"\u000f\u0001\u000f\u0001\u000f\u0001\u0010\u0001\u0010\u0001\u0010\u0001"+
|
||||
"\u0011\u0001\u0011\u0001\u0011\u0001\u0012\u0001\u0012\u0001\u0012\u0001"+
|
||||
"\u0012\u0001\u0012\u0001\u0013\u0001\u0013\u0001\u0013\u0001\u0013\u0001"+
|
||||
"\u0013\u0001\u0014\u0001\u0014\u0001\u0014\u0001\u0014\u0001\u0014\u0001"+
|
||||
"\u0014\u0001\u0014\u0001\u0014\u0001\u0015\u0001\u0015\u0001\u0016\u0001"+
|
||||
"\u0016\u0001\u0016\u0001\u0016\u0001\u0016\u0001\u0016\u0001\u0017\u0001"+
|
||||
"\u0017\u0001\u0017\u0001\u0017\u0001\u0018\u0001\u0018\u0001\u0018\u0001"+
|
||||
"\u0018\u0001\u0019\u0001\u0019\u0001\u0019\u0001\u0019\u0001\u001a\u0001"+
|
||||
"\u001a\u0001\u001a\u0001\u001a\u0001\u001b\u0001\u001b\u0001\u001b\u0001"+
|
||||
"\u001b\u0001\u001b\u0001\u001b\u0001\u001b\u0001\u001c\u0001\u001c\u0001"+
|
||||
"\u001c\u0001\u001c\u0001\u001c\u0001\u001c\u0001\u001c\u0001\u001d\u0001"+
|
||||
"\u001d\u0001\u001d\u0001\u001d\u0001\u001d\u0001\u001d\u0001\u001d\u0001"+
|
||||
"\u001d\u0001\u001d\u0001\u001e\u0001\u001e\u0001\u001e\u0001\u001e\u0001"+
|
||||
"\u001e\u0001\u001f\u0001\u001f\u0001\u001f\u0001\u001f\u0001\u001f\u0001"+
|
||||
"\u001f\u0001\u001f\u0001\u001f\u0001\u001f\u0001 \u0001 \u0001 \u0001"+
|
||||
" \u0001 \u0001 \u0001 \u0001 \u0001 \u0001 \u0001 \u0001 \u0001 \u0001"+
|
||||
" \u0001 \u0001 \u0001 \u0001 \u0001 \u0001!\u0001!\u0001!\u0001!\u0001"+
|
||||
"!\u0001\"\u0001\"\u0001\"\u0001\"\u0001\"\u0001\"\u0001\"\u0001\"\u0001"+
|
||||
"#\u0001#\u0001#\u0001#\u0001#\u0001#\u0001#\u0001#\u0001#\u0001$\u0001"+
|
||||
"$\u0001$\u0001%\u0001%\u0001%\u0001%\u0001%\u0001%\u0001%\u0001%\u0001"+
|
||||
"&\u0001&\u0001&\u0001&\u0001&\u0001&\u0001&\u0001&\u0001&\u0001&\u0001"+
|
||||
"&\u0001\'\u0001\'\u0001\'\u0001\'\u0001\'\u0001\'\u0001\'\u0001\'\u0001"+
|
||||
"\'\u0001\'\u0001\'\u0001\'\u0001(\u0001(\u0001(\u0001(\u0001(\u0001(\u0001"+
|
||||
"(\u0001(\u0001(\u0001)\u0001)\u0001)\u0001*\u0001*\u0001*\u0001*\u0001"+
|
||||
"+\u0001+\u0001+\u0001+\u0001+\u0001+\u0001+\u0001,\u0001,\u0001-\u0001"+
|
||||
"-\u0001-\u0001.\u0001.\u0001/\u0001/\u0001/\u00010\u00010\u00010\u0003"+
|
||||
"0\u01a3\b0\u00011\u00011\u00011\u00012\u00012\u00013\u00013\u00014\u0001"+
|
||||
"4\u00015\u00015\u00016\u00016\u00017\u00017\u00018\u00018\u00019\u0001"+
|
||||
"9\u0001:\u0001:\u0001;\u0001;\u0001<\u0004<\u01bd\b<\u000b<\f<\u01be\u0001"+
|
||||
"<\u0001<\u0004<\u01c3\b<\u000b<\f<\u01c4\u0003<\u01c7\b<\u0001<\u0001"+
|
||||
"<\u0003<\u01cb\b<\u0001<\u0004<\u01ce\b<\u000b<\f<\u01cf\u0001=\u0004"+
|
||||
"=\u01d3\b=\u000b=\f=\u01d4\u0001=\u0001=\u0004=\u01d9\b=\u000b=\f=\u01da"+
|
||||
"\u0001>\u0004>\u01de\b>\u000b>\f>\u01df\u0001?\u0004?\u01e3\b?\u000b?"+
|
||||
"\f?\u01e4\u0001?\u0004?\u01e8\b?\u000b?\f?\u01e9\u0001@\u0001@\u0001@"+
|
||||
"\u0001@\u0005@\u01f0\b@\n@\f@\u01f3\t@\u0001@\u0001@\u0001@\u0001@\u0001"+
|
||||
"@\u0005@\u01fa\b@\n@\f@\u01fd\t@\u0001@\u0003@\u0200\b@\u0001A\u0001A"+
|
||||
"\u0005A\u0204\bA\nA\fA\u0207\tA\u0001B\u0004B\u020a\bB\u000bB\fB\u020b"+
|
||||
"\u0001B\u0001B\u0001C\u0001C\u0001C\u0001C\u0005C\u0214\bC\nC\fC\u0217"+
|
||||
"\tC\u0001C\u0001C\u0001D\u0001D\u0001D\u0001D\u0005D\u021f\bD\nD\fD\u0222"+
|
||||
"\tD\u0001D\u0001D\u0001D\u0001D\u0001D\u0001\u0220\u0000E\u0001\u0001"+
|
||||
"\u0003\u0002\u0005\u0003\u0007\u0004\t\u0005\u000b\u0006\r\u0007\u000f"+
|
||||
"\b\u0011\t\u0013\n\u0015\u000b\u0017\f\u0019\r\u001b\u000e\u001d\u000f"+
|
||||
"\u001f\u0010!\u0011#\u0012%\u0013\'\u0014)\u0015+\u0016-\u0017/\u0018"+
|
||||
"1\u00193\u001a5\u001b7\u001c9\u001d;\u001e=\u001f? A!C\"E#G$I%K&M\'O("+
|
||||
"Q)S*U+W,Y-[.]/_0a1c2e3g4i5k6m7o8q9s:u;w<y={>}?\u007f@\u0081A\u0083B\u0085"+
|
||||
"C\u0087D\u0089E\u0001\u0000!\u0002\u0000AAaa\u0002\u0000NNnn\u0002\u0000"+
|
||||
"DDdd\u0002\u0000OOoo\u0002\u0000RRrr\u0002\u0000UUuu\u0002\u0000LLll\u0002"+
|
||||
"\u0000EEee\u0002\u0000SSss\u0002\u0000TTtt\u0002\u0000CCcc\u0002\u0000"+
|
||||
"FFff\u0002\u0000MMmm\u0002\u0000WWww\u0002\u0000HHhh\u0002\u0000GGgg\u0002"+
|
||||
"\u0000PPpp\u0002\u0000BBbb\u0002\u0000YYyy\u0002\u0000VVvv\u0002\u0000"+
|
||||
"IIii\u0002\u0000KKkk\u0002\u0000XXxx\u0002\u0000QQqq\u0001\u000009\u0002"+
|
||||
"\u0000++--\u0002\u0000AZaz\u0004\u0000\n\n\r\r\"\"\\\\\u0004\u0000\n\n"+
|
||||
"\r\r\'\'\\\\\u0003\u0000AZ__az\n\u0000\"\"-.09==A[]]__a{}~\u8000\uff5e"+
|
||||
"\u8000\uff5e\u0003\u0000\t\n\r\r \u0002\u0000\n\n\r\r\u023b\u0000\u0001"+
|
||||
"\u0001\u0000\u0000\u0000\u0000\u0003\u0001\u0000\u0000\u0000\u0000\u0005"+
|
||||
"\u0001\u0000\u0000\u0000\u0000\u0007\u0001\u0000\u0000\u0000\u0000\t\u0001"+
|
||||
"\u0000\u0000\u0000\u0000\u000b\u0001\u0000\u0000\u0000\u0000\r\u0001\u0000"+
|
||||
"\u0000\u0000\u0000\u000f\u0001\u0000\u0000\u0000\u0000\u0011\u0001\u0000"+
|
||||
"\u0000\u0000\u0000\u0013\u0001\u0000\u0000\u0000\u0000\u0015\u0001\u0000"+
|
||||
"\u0000\u0000\u0000\u0017\u0001\u0000\u0000\u0000\u0000\u0019\u0001\u0000"+
|
||||
"\u0000\u0000\u0000\u001b\u0001\u0000\u0000\u0000\u0000\u001d\u0001\u0000"+
|
||||
"\u0000\u0000\u0000\u001f\u0001\u0000\u0000\u0000\u0000!\u0001\u0000\u0000"+
|
||||
"\u0000\u0000#\u0001\u0000\u0000\u0000\u0000%\u0001\u0000\u0000\u0000\u0000"+
|
||||
"\'\u0001\u0000\u0000\u0000\u0000)\u0001\u0000\u0000\u0000\u0000+\u0001"+
|
||||
"\u0000\u0000\u0000\u0000-\u0001\u0000\u0000\u0000\u0000/\u0001\u0000\u0000"+
|
||||
"\u0000\u00001\u0001\u0000\u0000\u0000\u00003\u0001\u0000\u0000\u0000\u0000"+
|
||||
"5\u0001\u0000\u0000\u0000\u00007\u0001\u0000\u0000\u0000\u00009\u0001"+
|
||||
"\u0000\u0000\u0000\u0000;\u0001\u0000\u0000\u0000\u0000=\u0001\u0000\u0000"+
|
||||
"\u0000\u0000?\u0001\u0000\u0000\u0000\u0000A\u0001\u0000\u0000\u0000\u0000"+
|
||||
"C\u0001\u0000\u0000\u0000\u0000E\u0001\u0000\u0000\u0000\u0000G\u0001"+
|
||||
"\u0000\u0000\u0000\u0000I\u0001\u0000\u0000\u0000\u0000K\u0001\u0000\u0000"+
|
||||
"\u0000\u0000M\u0001\u0000\u0000\u0000\u0000O\u0001\u0000\u0000\u0000\u0000"+
|
||||
"Q\u0001\u0000\u0000\u0000\u0000S\u0001\u0000\u0000\u0000\u0000U\u0001"+
|
||||
"\u0000\u0000\u0000\u0000W\u0001\u0000\u0000\u0000\u0000Y\u0001\u0000\u0000"+
|
||||
"\u0000\u0000[\u0001\u0000\u0000\u0000\u0000]\u0001\u0000\u0000\u0000\u0000"+
|
||||
"_\u0001\u0000\u0000\u0000\u0000a\u0001\u0000\u0000\u0000\u0000c\u0001"+
|
||||
"\u0000\u0000\u0000\u0000e\u0001\u0000\u0000\u0000\u0000g\u0001\u0000\u0000"+
|
||||
"\u0000\u0000i\u0001\u0000\u0000\u0000\u0000k\u0001\u0000\u0000\u0000\u0000"+
|
||||
"m\u0001\u0000\u0000\u0000\u0000o\u0001\u0000\u0000\u0000\u0000q\u0001"+
|
||||
"\u0000\u0000\u0000\u0000s\u0001\u0000\u0000\u0000\u0000u\u0001\u0000\u0000"+
|
||||
"\u0000\u0000w\u0001\u0000\u0000\u0000\u0000y\u0001\u0000\u0000\u0000\u0000"+
|
||||
"{\u0001\u0000\u0000\u0000\u0000}\u0001\u0000\u0000\u0000\u0000\u007f\u0001"+
|
||||
"\u0000\u0000\u0000\u0000\u0081\u0001\u0000\u0000\u0000\u0000\u0083\u0001"+
|
||||
"\u0000\u0000\u0000\u0000\u0085\u0001\u0000\u0000\u0000\u0000\u0087\u0001"+
|
||||
"\u0000\u0000\u0000\u0000\u0089\u0001\u0000\u0000\u0000\u0001\u008b\u0001"+
|
||||
"\u0000\u0000\u0000\u0003\u008f\u0001\u0000\u0000\u0000\u0005\u0092\u0001"+
|
||||
"\u0000\u0000\u0000\u0007\u0099\u0001\u0000\u0000\u0000\t\u009d\u0001\u0000"+
|
||||
"\u0000\u0000\u000b\u00a4\u0001\u0000\u0000\u0000\r\u00a9\u0001\u0000\u0000"+
|
||||
"\u0000\u000f\u00af\u0001\u0000\u0000\u0000\u0011\u00b5\u0001\u0000\u0000"+
|
||||
"\u0000\u0013\u00b8\u0001\u0000\u0000\u0000\u0015\u00bf\u0001\u0000\u0000"+
|
||||
"\u0000\u0017\u00c5\u0001\u0000\u0000\u0000\u0019\u00cb\u0001\u0000\u0000"+
|
||||
"\u0000\u001b\u00d2\u0001\u0000\u0000\u0000\u001d\u00d5\u0001\u0000\u0000"+
|
||||
"\u0000\u001f\u00d9\u0001\u0000\u0000\u0000!\u00de\u0001\u0000\u0000\u0000"+
|
||||
"#\u00e1\u0001\u0000\u0000\u0000%\u00e4\u0001\u0000\u0000\u0000\'\u00e9"+
|
||||
"\u0001\u0000\u0000\u0000)\u00ee\u0001\u0000\u0000\u0000+\u00f6\u0001\u0000"+
|
||||
"\u0000\u0000-\u00f8\u0001\u0000\u0000\u0000/\u00fe\u0001\u0000\u0000\u0000"+
|
||||
"1\u0102\u0001\u0000\u0000\u00003\u0106\u0001\u0000\u0000\u00005\u010a"+
|
||||
"\u0001\u0000\u0000\u00007\u010e\u0001\u0000\u0000\u00009\u0115\u0001\u0000"+
|
||||
"\u0000\u0000;\u011c\u0001\u0000\u0000\u0000=\u0125\u0001\u0000\u0000\u0000"+
|
||||
"?\u012a\u0001\u0000\u0000\u0000A\u0133\u0001\u0000\u0000\u0000C\u0146"+
|
||||
"\u0001\u0000\u0000\u0000E\u014b\u0001\u0000\u0000\u0000G\u0153\u0001\u0000"+
|
||||
"\u0000\u0000I\u015c\u0001\u0000\u0000\u0000K\u015f\u0001\u0000\u0000\u0000"+
|
||||
"M\u0167\u0001\u0000\u0000\u0000O\u0172\u0001\u0000\u0000\u0000Q\u017e"+
|
||||
"\u0001\u0000\u0000\u0000S\u0187\u0001\u0000\u0000\u0000U\u018a\u0001\u0000"+
|
||||
"\u0000\u0000W\u018e\u0001\u0000\u0000\u0000Y\u0195\u0001\u0000\u0000\u0000"+
|
||||
"[\u0197\u0001\u0000\u0000\u0000]\u019a\u0001\u0000\u0000\u0000_\u019c"+
|
||||
"\u0001\u0000\u0000\u0000a\u01a2\u0001\u0000\u0000\u0000c\u01a4\u0001\u0000"+
|
||||
"\u0000\u0000e\u01a7\u0001\u0000\u0000\u0000g\u01a9\u0001\u0000\u0000\u0000"+
|
||||
"i\u01ab\u0001\u0000\u0000\u0000k\u01ad\u0001\u0000\u0000\u0000m\u01af"+
|
||||
"\u0001\u0000\u0000\u0000o\u01b1\u0001\u0000\u0000\u0000q\u01b3\u0001\u0000"+
|
||||
"\u0000\u0000s\u01b5\u0001\u0000\u0000\u0000u\u01b7\u0001\u0000\u0000\u0000"+
|
||||
"w\u01b9\u0001\u0000\u0000\u0000y\u01bc\u0001\u0000\u0000\u0000{\u01d2"+
|
||||
"\u0001\u0000\u0000\u0000}\u01dd\u0001\u0000\u0000\u0000\u007f\u01e2\u0001"+
|
||||
"\u0000\u0000\u0000\u0081\u01ff\u0001\u0000\u0000\u0000\u0083\u0201\u0001"+
|
||||
"\u0000\u0000\u0000\u0085\u0209\u0001\u0000\u0000\u0000\u0087\u020f\u0001"+
|
||||
"\u0000\u0000\u0000\u0089\u021a\u0001\u0000\u0000\u0000\u008b\u008c\u0007"+
|
||||
"\u0000\u0000\u0000\u008c\u008d\u0007\u0001\u0000\u0000\u008d\u008e\u0007"+
|
||||
"\u0002\u0000\u0000\u008e\u0002\u0001\u0000\u0000\u0000\u008f\u0090\u0007"+
|
||||
"\u0003\u0000\u0000\u0090\u0091\u0007\u0004\u0000\u0000\u0091\u0004\u0001"+
|
||||
"\u0000\u0000\u0000\u0092\u0093\u0007\u0005\u0000\u0000\u0093\u0094\u0007"+
|
||||
"\u0001\u0000\u0000\u0094\u0095\u0007\u0006\u0000\u0000\u0095\u0096\u0007"+
|
||||
"\u0007\u0000\u0000\u0096\u0097\u0007\b\u0000\u0000\u0097\u0098\u0007\b"+
|
||||
"\u0000\u0000\u0098\u0006\u0001\u0000\u0000\u0000\u0099\u009a\u0007\u0001"+
|
||||
"\u0000\u0000\u009a\u009b\u0007\u0003\u0000\u0000\u009b\u009c\u0007\t\u0000"+
|
||||
"\u0000\u009c\b\u0001\u0000\u0000\u0000\u009d\u009e\u0007\b\u0000\u0000"+
|
||||
"\u009e\u009f\u0007\u0007\u0000\u0000\u009f\u00a0\u0007\u0006\u0000\u0000"+
|
||||
"\u00a0\u00a1\u0007\u0007\u0000\u0000\u00a1\u00a2\u0007\n\u0000\u0000\u00a2"+
|
||||
"\u00a3\u0007\t\u0000\u0000\u00a3\n\u0001\u0000\u0000\u0000\u00a4\u00a5"+
|
||||
"\u0007\u000b\u0000\u0000\u00a5\u00a6\u0007\u0004\u0000\u0000\u00a6\u00a7"+
|
||||
"\u0007\u0003\u0000\u0000\u00a7\u00a8\u0007\f\u0000\u0000\u00a8\f\u0001"+
|
||||
"\u0000\u0000\u0000\u00a9\u00aa\u0007\r\u0000\u0000\u00aa\u00ab\u0007\u000e"+
|
||||
"\u0000\u0000\u00ab\u00ac\u0007\u0007\u0000\u0000\u00ac\u00ad\u0007\u0004"+
|
||||
"\u0000\u0000\u00ad\u00ae\u0007\u0007\u0000\u0000\u00ae\u000e\u0001\u0000"+
|
||||
"\u0000\u0000\u00af\u00b0\u0007\u000f\u0000\u0000\u00b0\u00b1\u0007\u0004"+
|
||||
"\u0000\u0000\u00b1\u00b2\u0007\u0003\u0000\u0000\u00b2\u00b3\u0007\u0005"+
|
||||
"\u0000\u0000\u00b3\u00b4\u0007\u0010\u0000\u0000\u00b4\u0010\u0001\u0000"+
|
||||
"\u0000\u0000\u00b5\u00b6\u0007\u0011\u0000\u0000\u00b6\u00b7\u0007\u0012"+
|
||||
"\u0000\u0000\u00b7\u0012\u0001\u0000\u0000\u0000\u00b8\u00b9\u0007\u000e"+
|
||||
"\u0000\u0000\u00b9\u00ba\u0007\u0000\u0000\u0000\u00ba\u00bb\u0007\u0013"+
|
||||
"\u0000\u0000\u00bb\u00bc\u0007\u0014\u0000\u0000\u00bc\u00bd\u0007\u0001"+
|
||||
"\u0000\u0000\u00bd\u00be\u0007\u000f\u0000\u0000\u00be\u0014\u0001\u0000"+
|
||||
"\u0000\u0000\u00bf\u00c0\u0007\u0003\u0000\u0000\u00c0\u00c1\u0007\u0004"+
|
||||
"\u0000\u0000\u00c1\u00c2\u0007\u0002\u0000\u0000\u00c2\u00c3\u0007\u0007"+
|
||||
"\u0000\u0000\u00c3\u00c4\u0007\u0004\u0000\u0000\u00c4\u0016\u0001\u0000"+
|
||||
"\u0000\u0000\u00c5\u00c6\u0007\u0006\u0000\u0000\u00c6\u00c7\u0007\u0014"+
|
||||
"\u0000\u0000\u00c7\u00c8\u0007\f\u0000\u0000\u00c8\u00c9\u0007\u0014\u0000"+
|
||||
"\u0000\u00c9\u00ca\u0007\t\u0000\u0000\u00ca\u0018\u0001\u0000\u0000\u0000"+
|
||||
"\u00cb\u00cc\u0007\u0003\u0000\u0000\u00cc\u00cd\u0007\u000b\u0000\u0000"+
|
||||
"\u00cd\u00ce\u0007\u000b\u0000\u0000\u00ce\u00cf\u0007\b\u0000\u0000\u00cf"+
|
||||
"\u00d0\u0007\u0007\u0000\u0000\u00d0\u00d1\u0007\t\u0000\u0000\u00d1\u001a"+
|
||||
"\u0001\u0000\u0000\u0000\u00d2\u00d3\u0007\u0000\u0000\u0000\u00d3\u00d4"+
|
||||
"\u0007\b\u0000\u0000\u00d4\u001c\u0001\u0000\u0000\u0000\u00d5\u00d6\u0007"+
|
||||
"\u0000\u0000\u0000\u00d6\u00d7\u0007\b\u0000\u0000\u00d7\u00d8\u0007\n"+
|
||||
"\u0000\u0000\u00d8\u001e\u0001\u0000\u0000\u0000\u00d9\u00da\u0007\u0002"+
|
||||
"\u0000\u0000\u00da\u00db\u0007\u0007\u0000\u0000\u00db\u00dc\u0007\b\u0000"+
|
||||
"\u0000\u00dc\u00dd\u0007\n\u0000\u0000\u00dd \u0001\u0000\u0000\u0000"+
|
||||
"\u00de\u00df\u0007\u0014\u0000\u0000\u00df\u00e0\u0007\u0001\u0000\u0000"+
|
||||
"\u00e0\"\u0001\u0000\u0000\u0000\u00e1\u00e2\u0007\u0014\u0000\u0000\u00e2"+
|
||||
"\u00e3\u0007\b\u0000\u0000\u00e3$\u0001\u0000\u0000\u0000\u00e4\u00e5"+
|
||||
"\u0007\u0001\u0000\u0000\u00e5\u00e6\u0007\u0005\u0000\u0000\u00e6\u00e7"+
|
||||
"\u0007\u0006\u0000\u0000\u00e7\u00e8\u0007\u0006\u0000\u0000\u00e8&\u0001"+
|
||||
"\u0000\u0000\u0000\u00e9\u00ea\u0007\u0006\u0000\u0000\u00ea\u00eb\u0007"+
|
||||
"\u0014\u0000\u0000\u00eb\u00ec\u0007\u0015\u0000\u0000\u00ec\u00ed\u0007"+
|
||||
"\u0007\u0000\u0000\u00ed(\u0001\u0000\u0000\u0000\u00ee\u00ef\u0007\u0011"+
|
||||
"\u0000\u0000\u00ef\u00f0\u0007\u0007\u0000\u0000\u00f0\u00f1\u0007\t\u0000"+
|
||||
"\u0000\u00f1\u00f2\u0007\r\u0000\u0000\u00f2\u00f3\u0007\u0007\u0000\u0000"+
|
||||
"\u00f3\u00f4\u0007\u0007\u0000\u0000\u00f4\u00f5\u0007\u0001\u0000\u0000"+
|
||||
"\u00f5*\u0001\u0000\u0000\u0000\u00f6\u00f7\u0005*\u0000\u0000\u00f7,"+
|
||||
"\u0001\u0000\u0000\u0000\u00f8\u00f9\u0007\n\u0000\u0000\u00f9\u00fa\u0007"+
|
||||
"\u0003\u0000\u0000\u00fa\u00fb\u0007\u0005\u0000\u0000\u00fb\u00fc\u0007"+
|
||||
"\u0001\u0000\u0000\u00fc\u00fd\u0007\t\u0000\u0000\u00fd.\u0001\u0000"+
|
||||
"\u0000\u0000\u00fe\u00ff\u0007\b\u0000\u0000\u00ff\u0100\u0007\u0005\u0000"+
|
||||
"\u0000\u0100\u0101\u0007\f\u0000\u0000\u01010\u0001\u0000\u0000\u0000"+
|
||||
"\u0102\u0103\u0007\u0000\u0000\u0000\u0103\u0104\u0007\u0013\u0000\u0000"+
|
||||
"\u0104\u0105\u0007\u000f\u0000\u0000\u01052\u0001\u0000\u0000\u0000\u0106"+
|
||||
"\u0107\u0007\f\u0000\u0000\u0107\u0108\u0007\u0014\u0000\u0000\u0108\u0109"+
|
||||
"\u0007\u0001\u0000\u0000\u01094\u0001\u0000\u0000\u0000\u010a\u010b\u0007"+
|
||||
"\f\u0000\u0000\u010b\u010c\u0007\u0000\u0000\u0000\u010c\u010d\u0007\u0016"+
|
||||
"\u0000\u0000\u010d6\u0001\u0000\u0000\u0000\u010e\u010f\u0007\b\u0000"+
|
||||
"\u0000\u010f\u0110\u0007\t\u0000\u0000\u0110\u0111\u0007\u0002\u0000\u0000"+
|
||||
"\u0111\u0112\u0007\u0002\u0000\u0000\u0112\u0113\u0007\u0007\u0000\u0000"+
|
||||
"\u0113\u0114\u0007\u0013\u0000\u0000\u01148\u0001\u0000\u0000\u0000\u0115"+
|
||||
"\u0116\u0007\b\u0000\u0000\u0116\u0117\u0007\t\u0000\u0000\u0117\u0118"+
|
||||
"\u0007\u0002\u0000\u0000\u0118\u0119\u0007\u0013\u0000\u0000\u0119\u011a"+
|
||||
"\u0007\u0000\u0000\u0000\u011a\u011b\u0007\u0004\u0000\u0000\u011b:\u0001"+
|
||||
"\u0000\u0000\u0000\u011c\u011d\u0007\u0013\u0000\u0000\u011d\u011e\u0007"+
|
||||
"\u0000\u0000\u0000\u011e\u011f\u0007\u0004\u0000\u0000\u011f\u0120\u0007"+
|
||||
"\u0014\u0000\u0000\u0120\u0121\u0007\u0000\u0000\u0000\u0121\u0122\u0007"+
|
||||
"\u0001\u0000\u0000\u0122\u0123\u0007\n\u0000\u0000\u0123\u0124\u0007\u0007"+
|
||||
"\u0000\u0000\u0124<\u0001\u0000\u0000\u0000\u0125\u0126\u0007\u0004\u0000"+
|
||||
"\u0000\u0126\u0127\u0007\u0000\u0000\u0000\u0127\u0128\u0007\t\u0000\u0000"+
|
||||
"\u0128\u0129\u0007\u0007\u0000\u0000\u0129>\u0001\u0000\u0000\u0000\u012a"+
|
||||
"\u012b\u0007\u0014\u0000\u0000\u012b\u012c\u0007\u0001\u0000\u0000\u012c"+
|
||||
"\u012d\u0007\n\u0000\u0000\u012d\u012e\u0007\u0004\u0000\u0000\u012e\u012f"+
|
||||
"\u0007\u0007\u0000\u0000\u012f\u0130\u0007\u0000\u0000\u0000\u0130\u0131"+
|
||||
"\u0007\b\u0000\u0000\u0131\u0132\u0007\u0007\u0000\u0000\u0132@\u0001"+
|
||||
"\u0000\u0000\u0000\u0133\u0134\u0007\u000e\u0000\u0000\u0134\u0135\u0007"+
|
||||
"\u0014\u0000\u0000\u0135\u0136\u0007\b\u0000\u0000\u0136\u0137\u0007\t"+
|
||||
"\u0000\u0000\u0137\u0138\u0007\u0003\u0000\u0000\u0138\u0139\u0007\u000f"+
|
||||
"\u0000\u0000\u0139\u013a\u0007\u0004\u0000\u0000\u013a\u013b\u0007\u0000"+
|
||||
"\u0000\u0000\u013b\u013c\u0007\f\u0000\u0000\u013c\u013d\u0005_\u0000"+
|
||||
"\u0000\u013d\u013e\u0007\u0017\u0000\u0000\u013e\u013f\u0007\u0005\u0000"+
|
||||
"\u0000\u013f\u0140\u0007\u0000\u0000\u0000\u0140\u0141\u0007\u0001\u0000"+
|
||||
"\u0000\u0141\u0142\u0007\t\u0000\u0000\u0142\u0143\u0007\u0014\u0000\u0000"+
|
||||
"\u0143\u0144\u0007\u0006\u0000\u0000\u0144\u0145\u0007\u0007\u0000\u0000"+
|
||||
"\u0145B\u0001\u0000\u0000\u0000\u0146\u0147\u0007\t\u0000\u0000\u0147"+
|
||||
"\u0148\u0007\u0003\u0000\u0000\u0148\u0149\u0007\u0010\u0000\u0000\u0149"+
|
||||
"\u014a\u0007\u0015\u0000\u0000\u014aD\u0001\u0000\u0000\u0000\u014b\u014c"+
|
||||
"\u0007\u0011\u0000\u0000\u014c\u014d\u0007\u0003\u0000\u0000\u014d\u014e"+
|
||||
"\u0007\t\u0000\u0000\u014e\u014f\u0007\t\u0000\u0000\u014f\u0150\u0007"+
|
||||
"\u0003\u0000\u0000\u0150\u0151\u0007\f\u0000\u0000\u0151\u0152\u0007\u0015"+
|
||||
"\u0000\u0000\u0152F\u0001\u0000\u0000\u0000\u0153\u0154\u0007\u0017\u0000"+
|
||||
"\u0000\u0154\u0155\u0007\u0005\u0000\u0000\u0155\u0156\u0007\u0000\u0000"+
|
||||
"\u0000\u0156\u0157\u0007\u0001\u0000\u0000\u0157\u0158\u0007\t\u0000\u0000"+
|
||||
"\u0158\u0159\u0007\u0014\u0000\u0000\u0159\u015a\u0007\u0006\u0000\u0000"+
|
||||
"\u015a\u015b\u0007\u0007\u0000\u0000\u015bH\u0001\u0000\u0000\u0000\u015c"+
|
||||
"\u015d\u0007\u0011\u0000\u0000\u015d\u015e\u0007\u0012\u0000\u0000\u015e"+
|
||||
"J\u0001\u0000\u0000\u0000\u015f\u0160\u0007\r\u0000\u0000\u0160\u0161"+
|
||||
"\u0007\u0014\u0000\u0000\u0161\u0162\u0007\t\u0000\u0000\u0162\u0163\u0007"+
|
||||
"\u000e\u0000\u0000\u0163\u0164\u0007\u0003\u0000\u0000\u0164\u0165\u0007"+
|
||||
"\u0005\u0000\u0000\u0165\u0166\u0007\t\u0000\u0000\u0166L\u0001\u0000"+
|
||||
"\u0000\u0000\u0167\u0168\u0007\u000f\u0000\u0000\u0168\u0169\u0007\u0004"+
|
||||
"\u0000\u0000\u0169\u016a\u0007\u0003\u0000\u0000\u016a\u016b\u0007\u0005"+
|
||||
"\u0000\u0000\u016b\u016c\u0007\u0010\u0000\u0000\u016c\u016d\u0005_\u0000"+
|
||||
"\u0000\u016d\u016e\u0007\u0006\u0000\u0000\u016e\u016f\u0007\u0007\u0000"+
|
||||
"\u0000\u016f\u0170\u0007\u000b\u0000\u0000\u0170\u0171\u0007\t\u0000\u0000"+
|
||||
"\u0171N\u0001\u0000\u0000\u0000\u0172\u0173\u0007\u000f\u0000\u0000\u0173"+
|
||||
"\u0174\u0007\u0004\u0000\u0000\u0174\u0175\u0007\u0003\u0000\u0000\u0175"+
|
||||
"\u0176\u0007\u0005\u0000\u0000\u0176\u0177\u0007\u0010\u0000\u0000\u0177"+
|
||||
"\u0178\u0005_\u0000\u0000\u0178\u0179\u0007\u0004\u0000\u0000\u0179\u017a"+
|
||||
"\u0007\u0014\u0000\u0000\u017a\u017b\u0007\u000f\u0000\u0000\u017b\u017c"+
|
||||
"\u0007\u000e\u0000\u0000\u017c\u017d\u0007\t\u0000\u0000\u017dP\u0001"+
|
||||
"\u0000\u0000\u0000\u017e\u017f\u0007\u0014\u0000\u0000\u017f\u0180\u0007"+
|
||||
"\u000f\u0000\u0000\u0180\u0181\u0007\u0001\u0000\u0000\u0181\u0182\u0007"+
|
||||
"\u0003\u0000\u0000\u0182\u0183\u0007\u0004\u0000\u0000\u0183\u0184\u0007"+
|
||||
"\u0014\u0000\u0000\u0184\u0185\u0007\u0001\u0000\u0000\u0185\u0186\u0007"+
|
||||
"\u000f\u0000\u0000\u0186R\u0001\u0000\u0000\u0000\u0187\u0188\u0007\u0003"+
|
||||
"\u0000\u0000\u0188\u0189\u0007\u0001\u0000\u0000\u0189T\u0001\u0000\u0000"+
|
||||
"\u0000\u018a\u018b\u0007\b\u0000\u0000\u018b\u018c\u0007\u0017\u0000\u0000"+
|
||||
"\u018c\u018d\u0007\u0006\u0000\u0000\u018dV\u0001\u0000\u0000\u0000\u018e"+
|
||||
"\u018f\u0007\u0010\u0000\u0000\u018f\u0190\u0007\u0004\u0000\u0000\u0190"+
|
||||
"\u0191\u0007\u0003\u0000\u0000\u0191\u0192\u0007\f\u0000\u0000\u0192\u0193"+
|
||||
"\u0007\u0017\u0000\u0000\u0193\u0194\u0007\u0006\u0000\u0000\u0194X\u0001"+
|
||||
"\u0000\u0000\u0000\u0195\u0196\u0005>\u0000\u0000\u0196Z\u0001\u0000\u0000"+
|
||||
"\u0000\u0197\u0198\u0005>\u0000\u0000\u0198\u0199\u0005=\u0000\u0000\u0199"+
|
||||
"\\\u0001\u0000\u0000\u0000\u019a\u019b\u0005<\u0000\u0000\u019b^\u0001"+
|
||||
"\u0000\u0000\u0000\u019c\u019d\u0005<\u0000\u0000\u019d\u019e\u0005=\u0000"+
|
||||
"\u0000\u019e`\u0001\u0000\u0000\u0000\u019f\u01a0\u0005=\u0000\u0000\u01a0"+
|
||||
"\u01a3\u0005=\u0000\u0000\u01a1\u01a3\u0005=\u0000\u0000\u01a2\u019f\u0001"+
|
||||
"\u0000\u0000\u0000\u01a2\u01a1\u0001\u0000\u0000\u0000\u01a3b\u0001\u0000"+
|
||||
"\u0000\u0000\u01a4\u01a5\u0005!\u0000\u0000\u01a5\u01a6\u0005=\u0000\u0000"+
|
||||
"\u01a6d\u0001\u0000\u0000\u0000\u01a7\u01a8\u0005(\u0000\u0000\u01a8f"+
|
||||
"\u0001\u0000\u0000\u0000\u01a9\u01aa\u0005)\u0000\u0000\u01aah\u0001\u0000"+
|
||||
"\u0000\u0000\u01ab\u01ac\u0005{\u0000\u0000\u01acj\u0001\u0000\u0000\u0000"+
|
||||
"\u01ad\u01ae\u0005}\u0000\u0000\u01ael\u0001\u0000\u0000\u0000\u01af\u01b0"+
|
||||
"\u0005[\u0000\u0000\u01b0n\u0001\u0000\u0000\u0000\u01b1\u01b2\u0005]"+
|
||||
"\u0000\u0000\u01b2p\u0001\u0000\u0000\u0000\u01b3\u01b4\u0005,\u0000\u0000"+
|
||||
"\u01b4r\u0001\u0000\u0000\u0000\u01b5\u01b6\u0005.\u0000\u0000\u01b6t"+
|
||||
"\u0001\u0000\u0000\u0000\u01b7\u01b8\u0005:\u0000\u0000\u01b8v\u0001\u0000"+
|
||||
"\u0000\u0000\u01b9\u01ba\u0005;\u0000\u0000\u01bax\u0001\u0000\u0000\u0000"+
|
||||
"\u01bb\u01bd\u0007\u0018\u0000\u0000\u01bc\u01bb\u0001\u0000\u0000\u0000"+
|
||||
"\u01bd\u01be\u0001\u0000\u0000\u0000\u01be\u01bc\u0001\u0000\u0000\u0000"+
|
||||
"\u01be\u01bf\u0001\u0000\u0000\u0000\u01bf\u01c6\u0001\u0000\u0000\u0000"+
|
||||
"\u01c0\u01c2\u0005.\u0000\u0000\u01c1\u01c3\u0007\u0018\u0000\u0000\u01c2"+
|
||||
"\u01c1\u0001\u0000\u0000\u0000\u01c3\u01c4\u0001\u0000\u0000\u0000\u01c4"+
|
||||
"\u01c2\u0001\u0000\u0000\u0000\u01c4\u01c5\u0001\u0000\u0000\u0000\u01c5"+
|
||||
"\u01c7\u0001\u0000\u0000\u0000\u01c6\u01c0\u0001\u0000\u0000\u0000\u01c6"+
|
||||
"\u01c7\u0001\u0000\u0000\u0000\u01c7\u01c8\u0001\u0000\u0000\u0000\u01c8"+
|
||||
"\u01ca\u0007\u0007\u0000\u0000\u01c9\u01cb\u0007\u0019\u0000\u0000\u01ca"+
|
||||
"\u01c9\u0001\u0000\u0000\u0000\u01ca\u01cb\u0001\u0000\u0000\u0000\u01cb"+
|
||||
"\u01cd\u0001\u0000\u0000\u0000\u01cc\u01ce\u0007\u0018\u0000\u0000\u01cd"+
|
||||
"\u01cc\u0001\u0000\u0000\u0000\u01ce\u01cf\u0001\u0000\u0000\u0000\u01cf"+
|
||||
"\u01cd\u0001\u0000\u0000\u0000\u01cf\u01d0\u0001\u0000\u0000\u0000\u01d0"+
|
||||
"z\u0001\u0000\u0000\u0000\u01d1\u01d3\u0007\u0018\u0000\u0000\u01d2\u01d1"+
|
||||
"\u0001\u0000\u0000\u0000\u01d3\u01d4\u0001\u0000\u0000\u0000\u01d4\u01d2"+
|
||||
"\u0001\u0000\u0000\u0000\u01d4\u01d5\u0001\u0000\u0000\u0000\u01d5\u01d6"+
|
||||
"\u0001\u0000\u0000\u0000\u01d6\u01d8\u0005.\u0000\u0000\u01d7\u01d9\u0007"+
|
||||
"\u0018\u0000\u0000\u01d8\u01d7\u0001\u0000\u0000\u0000\u01d9\u01da\u0001"+
|
||||
"\u0000\u0000\u0000\u01da\u01d8\u0001\u0000\u0000\u0000\u01da\u01db\u0001"+
|
||||
"\u0000\u0000\u0000\u01db|\u0001\u0000\u0000\u0000\u01dc\u01de\u0007\u0018"+
|
||||
"\u0000\u0000\u01dd\u01dc\u0001\u0000\u0000\u0000\u01de\u01df\u0001\u0000"+
|
||||
"\u0000\u0000\u01df\u01dd\u0001\u0000\u0000\u0000\u01df\u01e0\u0001\u0000"+
|
||||
"\u0000\u0000\u01e0~\u0001\u0000\u0000\u0000\u01e1\u01e3\u0007\u0018\u0000"+
|
||||
"\u0000\u01e2\u01e1\u0001\u0000\u0000\u0000\u01e3\u01e4\u0001\u0000\u0000"+
|
||||
"\u0000\u01e4\u01e2\u0001\u0000\u0000\u0000\u01e4\u01e5\u0001\u0000\u0000"+
|
||||
"\u0000\u01e5\u01e7\u0001\u0000\u0000\u0000\u01e6\u01e8\u0007\u001a\u0000"+
|
||||
"\u0000\u01e7\u01e6\u0001\u0000\u0000\u0000\u01e8\u01e9\u0001\u0000\u0000"+
|
||||
"\u0000\u01e9\u01e7\u0001\u0000\u0000\u0000\u01e9\u01ea\u0001\u0000\u0000"+
|
||||
"\u0000\u01ea\u0080\u0001\u0000\u0000\u0000\u01eb\u01f1\u0005\"\u0000\u0000"+
|
||||
"\u01ec\u01f0\b\u001b\u0000\u0000\u01ed\u01ee\u0005\\\u0000\u0000\u01ee"+
|
||||
"\u01f0\t\u0000\u0000\u0000\u01ef\u01ec\u0001\u0000\u0000\u0000\u01ef\u01ed"+
|
||||
"\u0001\u0000\u0000\u0000\u01f0\u01f3\u0001\u0000\u0000\u0000\u01f1\u01ef"+
|
||||
"\u0001\u0000\u0000\u0000\u01f1\u01f2\u0001\u0000\u0000\u0000\u01f2\u01f4"+
|
||||
"\u0001\u0000\u0000\u0000\u01f3\u01f1\u0001\u0000\u0000\u0000\u01f4\u0200"+
|
||||
"\u0005\"\u0000\u0000\u01f5\u01fb\u0005\'\u0000\u0000\u01f6\u01fa\b\u001c"+
|
||||
"\u0000\u0000\u01f7\u01f8\u0005\\\u0000\u0000\u01f8\u01fa\t\u0000\u0000"+
|
||||
"\u0000\u01f9\u01f6\u0001\u0000\u0000\u0000\u01f9\u01f7\u0001\u0000\u0000"+
|
||||
"\u0000\u01fa\u01fd\u0001\u0000\u0000\u0000\u01fb\u01f9\u0001\u0000\u0000"+
|
||||
"\u0000\u01fb\u01fc\u0001\u0000\u0000\u0000\u01fc\u01fe\u0001\u0000\u0000"+
|
||||
"\u0000\u01fd\u01fb\u0001\u0000\u0000\u0000\u01fe\u0200\u0005\'\u0000\u0000"+
|
||||
"\u01ff\u01eb\u0001\u0000\u0000\u0000\u01ff\u01f5\u0001\u0000\u0000\u0000"+
|
||||
"\u0200\u0082\u0001\u0000\u0000\u0000\u0201\u0205\u0007\u001d\u0000\u0000"+
|
||||
"\u0202\u0204\u0007\u001e\u0000\u0000\u0203\u0202\u0001\u0000\u0000\u0000"+
|
||||
"\u0204\u0207\u0001\u0000\u0000\u0000\u0205\u0203\u0001\u0000\u0000\u0000"+
|
||||
"\u0205\u0206\u0001\u0000\u0000\u0000\u0206\u0084\u0001\u0000\u0000\u0000"+
|
||||
"\u0207\u0205\u0001\u0000\u0000\u0000\u0208\u020a\u0007\u001f\u0000\u0000"+
|
||||
"\u0209\u0208\u0001\u0000\u0000\u0000\u020a\u020b\u0001\u0000\u0000\u0000"+
|
||||
"\u020b\u0209\u0001\u0000\u0000\u0000\u020b\u020c\u0001\u0000\u0000\u0000"+
|
||||
"\u020c\u020d\u0001\u0000\u0000\u0000\u020d\u020e\u0006B\u0000\u0000\u020e"+
|
||||
"\u0086\u0001\u0000\u0000\u0000\u020f\u0210\u0005/\u0000\u0000\u0210\u0211"+
|
||||
"\u0005/\u0000\u0000\u0211\u0215\u0001\u0000\u0000\u0000\u0212\u0214\b"+
|
||||
" \u0000\u0000\u0213\u0212\u0001\u0000\u0000\u0000\u0214\u0217\u0001\u0000"+
|
||||
"\u0000\u0000\u0215\u0213\u0001\u0000\u0000\u0000\u0215\u0216\u0001\u0000"+
|
||||
"\u0000\u0000\u0216\u0218\u0001\u0000\u0000\u0000\u0217\u0215\u0001\u0000"+
|
||||
"\u0000\u0000\u0218\u0219\u0006C\u0001\u0000\u0219\u0088\u0001\u0000\u0000"+
|
||||
"\u0000\u021a\u021b\u0005/\u0000\u0000\u021b\u021c\u0005*\u0000\u0000\u021c"+
|
||||
"\u0220\u0001\u0000\u0000\u0000\u021d\u021f\t\u0000\u0000\u0000\u021e\u021d"+
|
||||
"\u0001\u0000\u0000\u0000\u021f\u0222\u0001\u0000\u0000\u0000\u0220\u0221"+
|
||||
"\u0001\u0000\u0000\u0000\u0220\u021e\u0001\u0000\u0000\u0000\u0221\u0223"+
|
||||
"\u0001\u0000\u0000\u0000\u0222\u0220\u0001\u0000\u0000\u0000\u0223\u0224"+
|
||||
"\u0005*\u0000\u0000\u0224\u0225\u0005/\u0000\u0000\u0225\u0226\u0001\u0000"+
|
||||
"\u0000\u0000\u0226\u0227\u0006D\u0001\u0000\u0227\u008a\u0001\u0000\u0000"+
|
||||
"\u0000\u0015\u0000\u01a2\u01be\u01c4\u01c6\u01ca\u01cf\u01d4\u01da\u01df"+
|
||||
"\u01e4\u01e9\u01ef\u01f1\u01f9\u01fb\u01ff\u0205\u020b\u0215\u0220\u0002"+
|
||||
"\u0000\u0001\u0000\u0006\u0000\u0000";
|
||||
public static final ATN _ATN =
|
||||
new ATNDeserializer().deserialize(_serializedATN.toCharArray());
|
||||
new ATNDeserializer().deserialize(_serializedATN.toCharArray());
|
||||
static {
|
||||
_decisionToDFA = new DFA[_ATN.getNumberOfDecisions()];
|
||||
for (int i = 0; i < _ATN.getNumberOfDecisions(); i++) {
|
||||
|
||||
+2715
-191
File diff suppressed because it is too large
Load Diff
+229
-16
@@ -39,23 +39,15 @@ public interface AlertExpressionVisitor<T> extends ParseTreeVisitor<T> {
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by the {@code AndExpr}
|
||||
* labeled alternative in AlertExpressionParser#expr
|
||||
* labeled alternative in {@link AlertExpressionParser#expr}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitAndExpr(AlertExpressionParser.AndExprContext ctx);
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by the {@code QueryExpr}
|
||||
* labeled alternative in AlertExpressionParser#expr
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitQueryExpr(AlertExpressionParser.QueryExprContext ctx);
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by the {@code ComparisonExpr}
|
||||
* labeled alternative in AlertExpressionParser#expr
|
||||
* labeled alternative in {@link AlertExpressionParser#expr}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
@@ -63,15 +55,31 @@ public interface AlertExpressionVisitor<T> extends ParseTreeVisitor<T> {
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by the {@code UnlessExpr}
|
||||
* labeled alternative in AlertExpressionParser#expr
|
||||
* labeled alternative in {@link AlertExpressionParser#expr}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitUnlessExpr(AlertExpressionParser.UnlessExprContext ctx);
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by the {@code SqlExpr}
|
||||
* labeled alternative in {@link AlertExpressionParser#expr}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitSqlExpr(AlertExpressionParser.SqlExprContext ctx);
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by the {@code SqlCallExpr}
|
||||
* labeled alternative in {@link AlertExpressionParser#expr}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitSqlCallExpr(AlertExpressionParser.SqlCallExprContext ctx);
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by the {@code LiteralExpr}
|
||||
* labeled alternative in AlertExpressionParser#expr
|
||||
* labeled alternative in {@link AlertExpressionParser#expr}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
@@ -79,26 +87,56 @@ public interface AlertExpressionVisitor<T> extends ParseTreeVisitor<T> {
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by the {@code ParenExpr}
|
||||
* labeled alternative in AlertExpressionParser#expr
|
||||
* labeled alternative in {@link AlertExpressionParser#expr}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitParenExpr(AlertExpressionParser.ParenExprContext ctx);
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by the {@code PromqlCallExpr}
|
||||
* labeled alternative in {@link AlertExpressionParser#expr}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitPromqlCallExpr(AlertExpressionParser.PromqlCallExprContext ctx);
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by the {@code PromqlExpr}
|
||||
* labeled alternative in {@link AlertExpressionParser#expr}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitPromqlExpr(AlertExpressionParser.PromqlExprContext ctx);
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by the {@code OrExpr}
|
||||
* labeled alternative in AlertExpressionParser#expr
|
||||
* labeled alternative in {@link AlertExpressionParser#expr}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitOrExpr(AlertExpressionParser.OrExprContext ctx);
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by {@link AlertExpressionParser#identifier}.
|
||||
* Visit a parse tree produced by {@link AlertExpressionParser#functionCall}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitIdentifier(AlertExpressionParser.IdentifierContext ctx);
|
||||
T visitFunctionCall(AlertExpressionParser.FunctionCallContext ctx);
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by {@link AlertExpressionParser#parameterList}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitParameterList(AlertExpressionParser.ParameterListContext ctx);
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by {@link AlertExpressionParser#parameter}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitParameter(AlertExpressionParser.ParameterContext ctx);
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by {@link AlertExpressionParser#number}.
|
||||
@@ -106,4 +144,179 @@ public interface AlertExpressionVisitor<T> extends ParseTreeVisitor<T> {
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitNumber(AlertExpressionParser.NumberContext ctx);
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by {@link AlertExpressionParser#string}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitString(AlertExpressionParser.StringContext ctx);
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by {@link AlertExpressionParser#duration}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitDuration(AlertExpressionParser.DurationContext ctx);
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by {@link AlertExpressionParser#functionName}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitFunctionName(AlertExpressionParser.FunctionNameContext ctx);
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by {@link AlertExpressionParser#selectSql}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitSelectSql(AlertExpressionParser.SelectSqlContext ctx);
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by {@link AlertExpressionParser#selectFieldList}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitSelectFieldList(AlertExpressionParser.SelectFieldListContext ctx);
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by {@link AlertExpressionParser#selectField}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitSelectField(AlertExpressionParser.SelectFieldContext ctx);
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by {@link AlertExpressionParser#groupByList}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitGroupByList(AlertExpressionParser.GroupByListContext ctx);
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by {@link AlertExpressionParser#orderByList}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitOrderByList(AlertExpressionParser.OrderByListContext ctx);
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by {@link AlertExpressionParser#orderByField}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitOrderByField(AlertExpressionParser.OrderByFieldContext ctx);
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by {@link AlertExpressionParser#limitClause}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitLimitClause(AlertExpressionParser.LimitClauseContext ctx);
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by {@link AlertExpressionParser#relList}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitRelList(AlertExpressionParser.RelListContext ctx);
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by {@link AlertExpressionParser#relation}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitRelation(AlertExpressionParser.RelationContext ctx);
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by {@link AlertExpressionParser#conditionList}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitConditionList(AlertExpressionParser.ConditionListContext ctx);
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by {@link AlertExpressionParser#compOp}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitCompOp(AlertExpressionParser.CompOpContext ctx);
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by {@link AlertExpressionParser#condition}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitCondition(AlertExpressionParser.ConditionContext ctx);
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by {@link AlertExpressionParser#conditionUnit}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitConditionUnit(AlertExpressionParser.ConditionUnitContext ctx);
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by {@link AlertExpressionParser#promql}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitPromql(AlertExpressionParser.PromqlContext ctx);
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by {@link AlertExpressionParser#metricSelector}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitMetricSelector(AlertExpressionParser.MetricSelectorContext ctx);
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by {@link AlertExpressionParser#labelMatcherList}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitLabelMatcherList(AlertExpressionParser.LabelMatcherListContext ctx);
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by {@link AlertExpressionParser#labelMatcherItem}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitLabelMatcherItem(AlertExpressionParser.LabelMatcherItemContext ctx);
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by {@link AlertExpressionParser#labelMatcherOp}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitLabelMatcherOp(AlertExpressionParser.LabelMatcherOpContext ctx);
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by {@link AlertExpressionParser#labelList}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitLabelList(AlertExpressionParser.LabelListContext ctx);
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by {@link AlertExpressionParser#instantVectorOp}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitInstantVectorOp(AlertExpressionParser.InstantVectorOpContext ctx);
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by {@link AlertExpressionParser#aggregationOperator}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitAggregationOperator(AlertExpressionParser.AggregationOperatorContext ctx);
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by {@link AlertExpressionParser#binaryOperator}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitBinaryOperator(AlertExpressionParser.BinaryOperatorContext ctx);
|
||||
}
|
||||
+115
-110
@@ -17,8 +17,6 @@
|
||||
|
||||
package org.apache.hertzbeat.alert.notice.impl;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@@ -29,11 +27,14 @@ 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.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Send alert information through FeiShu
|
||||
*/
|
||||
@@ -47,19 +48,18 @@ final class FlyBookAlertNotifyHandlerImpl extends AbstractAlertNotifyHandlerImpl
|
||||
*/
|
||||
private static final String[] TITLE_COLOR = {"red", "yellow", "orange"};
|
||||
|
||||
|
||||
@Override
|
||||
public void send(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) {
|
||||
try {
|
||||
String notificationContent = JsonUtil.toJson(renderContent(noticeTemplate, alert));
|
||||
// todo priority custom the color
|
||||
// todo priority custom the color
|
||||
String cardMessage = createLarkMessage(receiver.getUserId(), notificationContent, (byte) 1);
|
||||
String webHookUrl = alerterProperties.getFlyBookWebhookUrl() + receiver.getAccessToken();
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
HttpEntity<String> flyEntity = new HttpEntity<>(cardMessage, headers);
|
||||
ResponseEntity<CommonRobotNotifyResp> entity = restTemplate.postForEntity(webHookUrl,
|
||||
flyEntity, CommonRobotNotifyResp.class);
|
||||
flyEntity, CommonRobotNotifyResp.class);
|
||||
if (entity.getStatusCode() == HttpStatus.OK) {
|
||||
assert entity.getBody() != null;
|
||||
if (entity.getBody().getCode() == null || entity.getBody().getCode() == 0) {
|
||||
@@ -87,115 +87,120 @@ final class FlyBookAlertNotifyHandlerImpl extends AbstractAlertNotifyHandlerImpl
|
||||
*/
|
||||
private String createLarkMessage(String userId, String notificationContent, byte priority) {
|
||||
String larkCardMessage = """
|
||||
{
|
||||
"msg_type": "interactive",
|
||||
"card": {
|
||||
"config": {
|
||||
"update_multi": true
|
||||
},
|
||||
"i18n_elements": {
|
||||
"zh_cn": [
|
||||
{
|
||||
"tag": "column_set",
|
||||
"flex_mode": "none",
|
||||
"horizontal_spacing": "default",
|
||||
"background_style": "default",
|
||||
"columns": [
|
||||
{
|
||||
"tag": "column",
|
||||
"elements": [
|
||||
{
|
||||
"tag": "div",
|
||||
"text": {
|
||||
"tag": "plain_text",
|
||||
"content": "",
|
||||
"text_size": "normal",
|
||||
"text_align": "left",
|
||||
"text_color": "default"
|
||||
}
|
||||
}
|
||||
],
|
||||
"width": "weighted",
|
||||
"weight": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tag": "column_set",
|
||||
"flex_mode": "none",
|
||||
"horizontal_spacing": "default",
|
||||
"background_style": "default",
|
||||
"columns": [
|
||||
{
|
||||
"tag": "column",
|
||||
"elements": [
|
||||
{
|
||||
"tag": "div",
|
||||
"text": {
|
||||
"tag": "plain_text",
|
||||
"content": %s,
|
||||
"text_size": "normal",
|
||||
"text_align": "left",
|
||||
"text_color": "default"
|
||||
}
|
||||
}
|
||||
],
|
||||
"width": "weighted",
|
||||
"weight": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
%s
|
||||
{
|
||||
"tag": "action",
|
||||
"actions": [
|
||||
{
|
||||
"tag": "button",
|
||||
"text": {
|
||||
"tag": "plain_text",
|
||||
"content": "登入控制台"
|
||||
},
|
||||
"type": "default",
|
||||
"complex_interaction": true,
|
||||
"width": "default",
|
||||
"size": "medium",
|
||||
"multi_url": {
|
||||
"url": "%s"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"i18n_header": {
|
||||
"zh_cn": {
|
||||
"title": {
|
||||
"tag": "plain_text",
|
||||
"content": "HertzBeat 告警"
|
||||
},
|
||||
"template": "%s"
|
||||
}
|
||||
}
|
||||
{
|
||||
"msg_type": "interactive",
|
||||
"card": {
|
||||
"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": "%s",
|
||||
"padding": "12px 12px 12px 12px"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
""";
|
||||
|
||||
String atUserElement = "";
|
||||
if (StringUtils.isNotBlank(userId)) {
|
||||
String atUserId = Arrays.stream(userId.split(","))
|
||||
.map(id -> "<at id=" + id + "></at>")
|
||||
.collect(Collectors.joining(" "));
|
||||
atUserElement = String.format("""
|
||||
{
|
||||
"tag": "div",
|
||||
"text": {
|
||||
"content": "%s",
|
||||
"tag": "lark_md"
|
||||
}
|
||||
},
|
||||
""", atUserId);
|
||||
atUserElement = "\\n" + Arrays.stream(userId.split(","))
|
||||
.map(id -> "<at id=" + id + "></at>")
|
||||
.collect(Collectors.joining(" "));
|
||||
}
|
||||
return String.format(larkCardMessage, notificationContent, atUserElement, alerterProperties.getConsoleUrl(), TITLE_COLOR[priority]);
|
||||
|
||||
if (notificationContent.startsWith("\"") && notificationContent.endsWith("\"")) {
|
||||
notificationContent = StringUtils.removeStart(notificationContent, "\"");
|
||||
notificationContent = StringUtils.removeEnd(notificationContent, "\"");
|
||||
}
|
||||
|
||||
return String.format(larkCardMessage,
|
||||
notificationContent.replace("\"", "\\\"") + atUserElement,
|
||||
alerterProperties.getConsoleUrl(), TITLE_COLOR[priority]);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+12
-1
@@ -31,6 +31,8 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Send alarm information through Server
|
||||
*/
|
||||
@@ -54,7 +56,16 @@ public class ServerChanAlertNotifyHandlerImpl extends AbstractAlertNotifyHandler
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
HttpEntity<ServerChanAlertNotifyHandlerImpl.ServerChanWebHookDto> httpEntity = new HttpEntity<>(serverChanWebHookDto, headers);
|
||||
String webHookUrl = String.format(alerterProperties.getServerChanWebhookUrl(), receiver.getServerChanToken());
|
||||
String sanitizedToken = receiver.getServerChanToken().replaceAll("[^a-zA-Z0-9_-]", "");
|
||||
String webHookUrl = String.format(alerterProperties.getServerChanWebhookUrl(), sanitizedToken);
|
||||
|
||||
// Validate the constructed URL against a whitelist
|
||||
List<String> allowedBaseUrls = List.of("https://api.serverchan.com", "https://serverchan.example.com");
|
||||
boolean isValidUrl = allowedBaseUrls.stream().anyMatch(webHookUrl::startsWith);
|
||||
if (!isValidUrl) {
|
||||
throw new AlertNoticeException("Invalid webhook URL: " + webHookUrl);
|
||||
}
|
||||
|
||||
ResponseEntity<CommonRobotNotifyResp> responseEntity = restTemplate.postForEntity(webHookUrl,
|
||||
httpEntity, CommonRobotNotifyResp.class);
|
||||
if (responseEntity.getStatusCode() == HttpStatus.OK) {
|
||||
|
||||
+24
-1
@@ -17,6 +17,7 @@
|
||||
|
||||
package org.apache.hertzbeat.alert.notice.impl;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Objects;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
@@ -52,7 +53,12 @@ final class SlackAlertNotifyHandlerImpl extends AbstractAlertNotifyHandlerImpl {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
HttpEntity<SlackNotifyDTO> slackNotifyEntity = new HttpEntity<>(slackNotify, headers);
|
||||
var entity = restTemplate.postForEntity(receiver.getSlackWebHookUrl(), slackNotifyEntity, String.class);
|
||||
String slackWebHookUrl = receiver.getSlackWebHookUrl();
|
||||
if (!isValidSlackWebHookUrl(slackWebHookUrl)) {
|
||||
log.warn("Invalid Slack Webhook URL: {}", slackWebHookUrl);
|
||||
throw new AlertNoticeException("Invalid Slack Webhook URL");
|
||||
}
|
||||
var entity = restTemplate.postForEntity(slackWebHookUrl, slackNotifyEntity, String.class);
|
||||
if (entity.getStatusCode() == HttpStatus.OK && entity.getBody() != null) {
|
||||
var body = entity.getBody();
|
||||
if (Objects.equals(SUCCESS, body)) {
|
||||
@@ -81,4 +87,21 @@ final class SlackAlertNotifyHandlerImpl extends AbstractAlertNotifyHandlerImpl {
|
||||
private String text;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validate if the Slack Webhook URL belongs to an allowed domain.
|
||||
*
|
||||
* @param url the Slack Webhook URL to validate
|
||||
* @return true if the URL is valid, false otherwise
|
||||
*/
|
||||
private boolean isValidSlackWebHookUrl(String url) {
|
||||
try {
|
||||
URI uri = new URI(url);
|
||||
String host = uri.getHost();
|
||||
return "hooks.slack.com".equals(host);
|
||||
} catch (Exception e) {
|
||||
log.warn("Error validating Slack Webhook URL: {}", url, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+15
-3
@@ -42,9 +42,14 @@ import org.springframework.stereotype.Component;
|
||||
final class TelegramBotAlertNotifyHandlerImpl extends AbstractAlertNotifyHandlerImpl {
|
||||
|
||||
@Override
|
||||
public void send(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) throws AlertNoticeException {
|
||||
public void send(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert)
|
||||
throws AlertNoticeException {
|
||||
try {
|
||||
String url = String.format(alerterProperties.getTelegramWebhookUrl(), receiver.getTgBotToken());
|
||||
String token = receiver.getTgBotToken();
|
||||
if (!isValidTelegramToken(token)) {
|
||||
throw new AlertNoticeException("Invalid Telegram Bot Token");
|
||||
}
|
||||
String url = String.format(alerterProperties.getTelegramWebhookUrl(), token);
|
||||
TelegramBotNotifyDTO notifyBody = TelegramBotNotifyDTO.builder()
|
||||
.chatId(receiver.getTgUserId())
|
||||
.text(renderContent(noticeTemplate, alert))
|
||||
@@ -54,7 +59,8 @@ final class TelegramBotAlertNotifyHandlerImpl extends AbstractAlertNotifyHandler
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
HttpEntity<TelegramBotNotifyDTO> telegramEntity = new HttpEntity<>(notifyBody, headers);
|
||||
ResponseEntity<TelegramBotNotifyResponse> entity = restTemplate.postForEntity(url, telegramEntity, TelegramBotNotifyResponse.class);
|
||||
ResponseEntity<TelegramBotNotifyResponse> entity = restTemplate.postForEntity(url, telegramEntity,
|
||||
TelegramBotNotifyResponse.class);
|
||||
if (entity.getStatusCode() == HttpStatus.OK && entity.getBody() != null) {
|
||||
TelegramBotNotifyResponse body = entity.getBody();
|
||||
if (body.ok) {
|
||||
@@ -99,4 +105,10 @@ final class TelegramBotAlertNotifyHandlerImpl extends AbstractAlertNotifyHandler
|
||||
private String description;
|
||||
}
|
||||
|
||||
private boolean isValidTelegramToken(String token) {
|
||||
// Adjusted pattern to match real Telegram Bot tokens like
|
||||
// 110201543:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw
|
||||
String tokenPattern = "^[0-9]+:[a-zA-Z0-9_-]+$";
|
||||
return token != null && token.matches(tokenPattern);
|
||||
}
|
||||
}
|
||||
|
||||
+17
-1
@@ -57,7 +57,12 @@ final class WeComRobotAlertNotifyHandlerImpl extends AbstractAlertNotifyHandlerI
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
HttpEntity<WeWorkWebHookDto> httpEntity = new HttpEntity<>(weWorkWebHookDTO, headers);
|
||||
String webHookUrl = alerterProperties.getWeWorkWebhookUrl() + receiver.getWechatId();
|
||||
String wechatId = receiver.getWechatId();
|
||||
if (!isValidWechatId(wechatId)) {
|
||||
log.warn("Invalid WeChat ID: {}", wechatId);
|
||||
throw new AlertNoticeException("Invalid WeChat ID provided.");
|
||||
}
|
||||
String webHookUrl = alerterProperties.getWeWorkWebhookUrl() + wechatId;
|
||||
ResponseEntity<CommonRobotNotifyResp> entity = restTemplate.postForEntity(webHookUrl, httpEntity, CommonRobotNotifyResp.class);
|
||||
if (entity.getStatusCode() == HttpStatus.OK) {
|
||||
assert entity.getBody() != null;
|
||||
@@ -170,4 +175,15 @@ final class WeComRobotAlertNotifyHandlerImpl extends AbstractAlertNotifyHandlerI
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the WeChat ID to ensure it meets the expected format.
|
||||
*
|
||||
* @param wechatId the WeChat ID to validate
|
||||
* @return true if valid, false otherwise
|
||||
*/
|
||||
private boolean isValidWechatId(String wechatId) {
|
||||
// Example validation: ensure the ID is alphanumeric and non-empty
|
||||
return StringUtils.isNotBlank(wechatId) && wechatId.matches("^[a-zA-Z0-9_-]+$");
|
||||
}
|
||||
}
|
||||
|
||||
+12
-3
@@ -18,12 +18,15 @@
|
||||
package org.apache.hertzbeat.alert.service;
|
||||
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
|
||||
import org.apache.hertzbeat.common.support.exception.AlertExpressionException;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Alarm define manager service
|
||||
*/
|
||||
@@ -107,5 +110,11 @@ public interface AlertDefineService {
|
||||
* @return Real-time alarm definition list
|
||||
*/
|
||||
List<AlertDefine> getRealTimeAlertDefines();
|
||||
|
||||
|
||||
/**
|
||||
* Get define preview
|
||||
* @return Data queried based on expressions
|
||||
* @throws AlertExpressionException expression error
|
||||
*/
|
||||
List<Map<String, Object>> getDefinePreview(String datasource, String type, String expr);
|
||||
}
|
||||
|
||||
+22
-4
@@ -17,7 +17,6 @@
|
||||
|
||||
package org.apache.hertzbeat.alert.service.impl;
|
||||
|
||||
import static org.apache.hertzbeat.common.constants.CommonConstants.ALERT_THRESHOLD_TYPE_REALTIME;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@@ -29,7 +28,9 @@ import org.apache.hertzbeat.alert.calculate.PeriodicAlertRuleScheduler;
|
||||
import org.apache.hertzbeat.alert.dao.AlertDefineDao;
|
||||
import org.apache.hertzbeat.alert.service.AlertDefineImExportService;
|
||||
import org.apache.hertzbeat.alert.service.AlertDefineService;
|
||||
import org.apache.hertzbeat.alert.service.DataSourceService;
|
||||
import org.apache.hertzbeat.common.cache.CacheFactory;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.constants.ExportFileConstants;
|
||||
import org.apache.hertzbeat.common.constants.SignConstants;
|
||||
import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
|
||||
@@ -72,18 +73,21 @@ public class AlertDefineServiceImpl implements AlertDefineService {
|
||||
@Autowired
|
||||
private PeriodicAlertRuleScheduler periodicAlertRuleScheduler;
|
||||
|
||||
private final DataSourceService dataSourceService;
|
||||
|
||||
private final Map<String, AlertDefineImExportService> alertDefineImExportServiceMap = new HashMap<>();
|
||||
|
||||
private static final String CONTENT_TYPE = MediaType.APPLICATION_OCTET_STREAM_VALUE + SignConstants.SINGLE_MARK + "charset=" + StandardCharsets.UTF_8;
|
||||
|
||||
public AlertDefineServiceImpl(List<AlertDefineImExportService> alertDefineImExportServiceList) {
|
||||
public AlertDefineServiceImpl(List<AlertDefineImExportService> alertDefineImExportServiceList, DataSourceService dataSourceService) {
|
||||
alertDefineImExportServiceList.forEach(it -> alertDefineImExportServiceMap.put(it.type(), it));
|
||||
this.dataSourceService = dataSourceService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(AlertDefine alertDefine, boolean isModify) throws IllegalArgumentException {
|
||||
if (StringUtils.hasText(alertDefine.getExpr())) {
|
||||
if (ALERT_THRESHOLD_TYPE_REALTIME.equals(alertDefine.getType())) {
|
||||
if (CommonConstants.ALERT_THRESHOLD_TYPE_REALTIME.equals(alertDefine.getType())) {
|
||||
try {
|
||||
JexlExpressionRunner.compile(alertDefine.getExpr());
|
||||
} catch (Exception e) {
|
||||
@@ -213,9 +217,23 @@ public class AlertDefineServiceImpl implements AlertDefineService {
|
||||
public List<AlertDefine> getRealTimeAlertDefines() {
|
||||
List<AlertDefine> alertDefines = CacheFactory.getAlertDefineCache();
|
||||
if (alertDefines == null) {
|
||||
alertDefines = alertDefineDao.findAlertDefinesByTypeAndEnableTrue(ALERT_THRESHOLD_TYPE_REALTIME);
|
||||
alertDefines = alertDefineDao.findAlertDefinesByTypeAndEnableTrue(CommonConstants.ALERT_THRESHOLD_TYPE_REALTIME);
|
||||
CacheFactory.setAlertDefineCache(alertDefines);
|
||||
}
|
||||
return alertDefines;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Map<String, Object>> getDefinePreview(String datasource, String type, String expr) {
|
||||
if (!StringUtils.hasText(expr) || !StringUtils.hasText(datasource) || !StringUtils.hasText(type)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
switch (type) {
|
||||
case CommonConstants.ALERT_THRESHOLD_TYPE_PERIODIC:
|
||||
return dataSourceService.calculate(datasource, expr);
|
||||
default:
|
||||
log.error("Get define preview unsupported type: {}", type);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* 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.service.impl;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.hertzbeat.alert.dto.AlibabaCloudSlsExternAlert;
|
||||
import org.apache.hertzbeat.alert.reduce.AlarmCommonReduce;
|
||||
import org.apache.hertzbeat.alert.service.ExternAlertService;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
|
||||
import org.apache.hertzbeat.common.util.IpDomainUtil;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Alibaba Cloud 'Simple Log Service(SLS)' external alarm service impl
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class AlibabaCloudSlsExternAlertService implements ExternAlertService {
|
||||
|
||||
private final AlarmCommonReduce alarmCommonReduce;
|
||||
|
||||
public AlibabaCloudSlsExternAlertService(AlarmCommonReduce alarmCommonReduce) {
|
||||
this.alarmCommonReduce = alarmCommonReduce;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addExternAlert(String content) {
|
||||
AlibabaCloudSlsExternAlert externAlert = JsonUtil.fromJson(content, AlibabaCloudSlsExternAlert.class);
|
||||
if (externAlert == null) {
|
||||
log.warn("Failure to parse external alert content. content: {}", content);
|
||||
return;
|
||||
}
|
||||
SingleAlert singleAlert = new AlibabaCloudSlsConverter().convert(externAlert);
|
||||
alarmCommonReduce.reduceAndSendAlarm(singleAlert);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String supportSource() {
|
||||
return "alibabacloud-sls";
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public static class AlibabaCloudSlsConverter {
|
||||
|
||||
/**
|
||||
* convert
|
||||
*
|
||||
* @param externAlert alert content entity
|
||||
* @return Single alert
|
||||
*/
|
||||
public SingleAlert convert(AlibabaCloudSlsExternAlert externAlert) {
|
||||
return SingleAlert.builder()
|
||||
.triggerTimes(1)
|
||||
.status(externAlert.getStatus())
|
||||
.startAt(Instant.ofEpochSecond(externAlert.getFireTime()).toEpochMilli())
|
||||
.activeAt(Instant.ofEpochSecond(externAlert.getAlertTime()).toEpochMilli())
|
||||
.endAt(convertResolveTime(externAlert.getStatus(), externAlert.getResolveTime()))
|
||||
.labels(buildLabels(externAlert))
|
||||
.annotations(buildAnnotations(externAlert))
|
||||
.content(formatContent(externAlert))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* todo i18n
|
||||
*
|
||||
* @param externAlert alert content entity
|
||||
* @return content
|
||||
*/
|
||||
private String formatContent(AlibabaCloudSlsExternAlert externAlert) {
|
||||
// convet severity
|
||||
Optional<AlibabaCloudSlsExternAlert.Severity> severity = AlibabaCloudSlsExternAlert.Severity.convert(externAlert.getSeverity());
|
||||
// If the alarm state is resolved, the value is the specific recovery time.
|
||||
Long resolveTimeMilli = convertResolveTime(externAlert.getStatus(), externAlert.getResolveTime());
|
||||
|
||||
return MessageFormat.format(
|
||||
"AlibabaCloud-sls alert , {0} - [{1}], level: [{2}], desc: {3}, fire_time:{4}, resolve_time:{5}",
|
||||
externAlert.getAnnotation("title"),
|
||||
externAlert.getStatus(),
|
||||
severity.isPresent() ? severity.get().getAlias() : "N/A",
|
||||
externAlert.getAnnotation("desc"),
|
||||
timeSecondToDate(Instant.ofEpochSecond(externAlert.getFireTime()).toEpochMilli()),
|
||||
null != resolveTimeMilli ? timeSecondToDate(resolveTimeMilli) : "N/A"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a timestamp (milliseconds) to a formatted date-time string.
|
||||
*
|
||||
* @param timestampMillis timestamp in milliseconds
|
||||
* @return formatted date-time string in the pattern: yyyy-MM-dd HH:mm:ss
|
||||
*/
|
||||
private String timeSecondToDate(long timestampMillis) {
|
||||
LocalDateTime dateTime = LocalDateTime.ofInstant(
|
||||
Instant.ofEpochMilli(timestampMillis),
|
||||
ZoneId.systemDefault()
|
||||
);
|
||||
return dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Build basic annotations and fill annotations for alibaba cloud sls.
|
||||
*
|
||||
* @param externAlert alert content entity
|
||||
* @return annotations
|
||||
*/
|
||||
private Map<String, String> buildAnnotations(AlibabaCloudSlsExternAlert externAlert) {
|
||||
Map<String, String> annotations = new HashMap<>(8);
|
||||
Optional<AlibabaCloudSlsExternAlert.Severity> severity = AlibabaCloudSlsExternAlert.Severity.convert(externAlert.getSeverity());
|
||||
severity.ifPresent(value -> annotations.put("severity", value.getAlias()));
|
||||
// Notification templates for sls need to be configured.
|
||||
if (StringUtils.isNotBlank(externAlert.getSigninUrl()) && IpDomainUtil.isHasSchema(externAlert.getSigninUrl())) {
|
||||
annotations.put("signinUrl", "<a target=\"_blank\" href=\"" + externAlert.getSigninUrl() + "\">View Details</a>");
|
||||
}
|
||||
// Filling the annotations with the alibaba cloud sls.
|
||||
if (null != externAlert.getAnnotations() && !externAlert.getAnnotations().isEmpty()) {
|
||||
annotations.putAll(externAlert.getAnnotations());
|
||||
}
|
||||
|
||||
return annotations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build basic labels and fill labels for alibaba cloud sls.
|
||||
*
|
||||
* @param externAlert alert content entity
|
||||
* @return labels
|
||||
*/
|
||||
private Map<String, String> buildLabels(AlibabaCloudSlsExternAlert externAlert) {
|
||||
Map<String, String> labels = new HashMap<>(8);
|
||||
labels.put("__source__", "alibabacloud-sls");
|
||||
labels.put("alertname", externAlert.getAlertName());
|
||||
labels.put("region", externAlert.getRegion());
|
||||
// The project name is globally unique.
|
||||
labels.put("project", externAlert.getProject());
|
||||
// Filling the labels with the alibaba cloud sls.
|
||||
if (null != externAlert.getLabels() && !externAlert.getLabels().isEmpty()){
|
||||
labels.putAll(externAlert.getLabels());
|
||||
}
|
||||
return labels;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the alarm status is firing, the value is 0.
|
||||
* If the alarm state is resolved, the value is the specific recovery time.
|
||||
*
|
||||
* @param status alert status
|
||||
* @param resolveTimeSecond recovery time
|
||||
* @return milliseconds
|
||||
*/
|
||||
private Long convertResolveTime(String status, int resolveTimeSecond) {
|
||||
return CommonConstants.ALERT_STATUS_RESOLVED.equals(status) ? Instant.ofEpochSecond(resolveTimeSecond).toEpochMilli() : null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+16
-7
@@ -24,11 +24,14 @@ import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.antlr.v4.runtime.CharStreams;
|
||||
import org.antlr.v4.runtime.CommonTokenStream;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.antlr.v4.runtime.tree.ParseTree;
|
||||
import org.apache.hertzbeat.alert.expr.AlertExpressionEvalVisitor;
|
||||
import org.apache.hertzbeat.alert.expr.AlertExpressionLexer;
|
||||
import org.apache.hertzbeat.alert.expr.AlertExpressionParser;
|
||||
import org.apache.hertzbeat.alert.service.DataSourceService;
|
||||
import org.apache.hertzbeat.common.support.exception.AlertExpressionException;
|
||||
import org.apache.hertzbeat.common.util.ResourceBundleUtil;
|
||||
import org.apache.hertzbeat.warehouse.db.QueryExecutor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -36,6 +39,7 @@ import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
@@ -45,6 +49,8 @@ import java.util.concurrent.TimeUnit;
|
||||
@Slf4j
|
||||
public class DataSourceServiceImpl implements DataSourceService {
|
||||
|
||||
protected ResourceBundle bundle = ResourceBundleUtil.getBundle("alerter");
|
||||
|
||||
@Setter
|
||||
@Autowired(required = false)
|
||||
private List<QueryExecutor> executors;
|
||||
@@ -80,6 +86,9 @@ public class DataSourceServiceImpl implements DataSourceService {
|
||||
expr = expr.replaceAll("\\s+", " ");
|
||||
try {
|
||||
return evaluate(expr, executor);
|
||||
} catch (AlertExpressionException ae) {
|
||||
log.error("Calculate query parse error {}: {}", datasource, ae.getMessage());
|
||||
throw ae;
|
||||
} catch (Exception e) {
|
||||
log.error("Error executing query on datasource {}: {}", datasource, e.getMessage());
|
||||
throw new RuntimeException("Query execution failed", e);
|
||||
@@ -87,14 +96,14 @@ public class DataSourceServiceImpl implements DataSourceService {
|
||||
}
|
||||
|
||||
private List<Map<String, Object>> evaluate(String expr, QueryExecutor executor) {
|
||||
ParseTree tree = expressionCache.get(expr, e -> {
|
||||
CommonTokenStream tokens = tokenStreamCache.get(e, this::createTokenStream);
|
||||
AlertExpressionParser parser = new AlertExpressionParser(tokens);
|
||||
return parser.expr();
|
||||
});
|
||||
AlertExpressionEvalVisitor visitor = new AlertExpressionEvalVisitor(executor);
|
||||
CommonTokenStream tokens = tokenStreamCache.get(expr, this::createTokenStream);
|
||||
AlertExpressionParser parser = new AlertExpressionParser(tokens);
|
||||
ParseTree tree = expressionCache.get(expr, e -> parser.expr());
|
||||
if (null != tokens && tokens.LA(1) != Token.EOF) {
|
||||
throw new AlertExpressionException(bundle.getString("alerter.calculate.parse.error"));
|
||||
}
|
||||
AlertExpressionEvalVisitor visitor = new AlertExpressionEvalVisitor(executor, tokens);
|
||||
return visitor.visit(tree);
|
||||
|
||||
}
|
||||
|
||||
private CommonTokenStream createTokenStream(String expr) {
|
||||
|
||||
+343
@@ -0,0 +1,343 @@
|
||||
/*
|
||||
* 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.service.impl;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.hertzbeat.alert.dto.HuaweiCloudExternAlert;
|
||||
import org.apache.hertzbeat.alert.reduce.AlarmCommonReduce;
|
||||
import org.apache.hertzbeat.alert.service.ExternAlertService;
|
||||
import org.apache.hertzbeat.alert.util.DateUtil;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
|
||||
import org.apache.hertzbeat.common.support.exception.IgnoreException;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Signature;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.text.MessageFormat;
|
||||
import java.time.Instant;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.apache.hertzbeat.alert.dto.HuaweiCloudExternAlert.AlertType.NOTIFICATION;
|
||||
import static org.apache.hertzbeat.alert.dto.HuaweiCloudExternAlert.AlertType.SUBSCRIPTION;
|
||||
import static org.apache.hertzbeat.alert.dto.HuaweiCloudExternAlert.AlertType.UNSUBSCRIBE;
|
||||
import static org.apache.hertzbeat.alert.dto.HuaweiCloudExternAlert.FIELD_MESSAGE;
|
||||
import static org.apache.hertzbeat.alert.dto.HuaweiCloudExternAlert.FIELD_MESSAGE_ID;
|
||||
import static org.apache.hertzbeat.alert.dto.HuaweiCloudExternAlert.FIELD_SUBJECT;
|
||||
import static org.apache.hertzbeat.alert.dto.HuaweiCloudExternAlert.FIELD_SUBSCRIBE_URL;
|
||||
import static org.apache.hertzbeat.alert.dto.HuaweiCloudExternAlert.FIELD_TIMESTAMP;
|
||||
import static org.apache.hertzbeat.alert.dto.HuaweiCloudExternAlert.FIELD_TOPIC_URN;
|
||||
import static org.apache.hertzbeat.alert.dto.HuaweiCloudExternAlert.FIELD_TYPE;
|
||||
|
||||
/**
|
||||
* Huawei cloud external alarm service impl
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class HuaweiCloudExternAlertService implements ExternAlertService {
|
||||
|
||||
private static final String CERTIFICATE_TYPE = "X.509";
|
||||
|
||||
private static final String CHARSET_UTF8 = StandardCharsets.UTF_8.name();
|
||||
|
||||
private final AlarmCommonReduce alarmCommonReduce;
|
||||
|
||||
public HuaweiCloudExternAlertService(AlarmCommonReduce alarmCommonReduce) {
|
||||
this.alarmCommonReduce = alarmCommonReduce;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addExternAlert(String content) {
|
||||
HuaweiCloudExternAlert externAlert = JsonUtil.fromJson(content, HuaweiCloudExternAlert.class);
|
||||
if (externAlert == null || StringUtils.isBlank(externAlert.getMessage())) {
|
||||
log.warn("Failure to parse external alert content. content: {}", content);
|
||||
return;
|
||||
}
|
||||
if (!isMessageValid(externAlert)) {
|
||||
log.warn("Huawei cloud alert verify failed. content: {}", content);
|
||||
return;
|
||||
}
|
||||
process(externAlert);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process according to different types
|
||||
*
|
||||
* @param externAlert alert content entity
|
||||
*/
|
||||
private void process(HuaweiCloudExternAlert externAlert) {
|
||||
if (NOTIFICATION.getType().equals(externAlert.getType())) {
|
||||
Optional.ofNullable(buildSendAlert(externAlert)).ifPresent(alarmCommonReduce::reduceAndSendAlarm);
|
||||
} else if (SUBSCRIPTION.getType().equals(externAlert.getType())) {
|
||||
autoSubscribeForUrl(externAlert.getSubscribeUrl());
|
||||
} else if (UNSUBSCRIBE.getType().equals(externAlert.getType())) {
|
||||
log.warn("Huawei cloud notifies the recipient of the notification to cancel the subscription.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build single alert.
|
||||
*
|
||||
* @param externAlert alert content entity
|
||||
* @return single alert
|
||||
*/
|
||||
private SingleAlert buildSendAlert(HuaweiCloudExternAlert externAlert) {
|
||||
HuaweiCloudExternAlert.AlertMessage message = JsonUtil.fromJson(externAlert.getMessage(), HuaweiCloudExternAlert.AlertMessage.class);
|
||||
if (null == message || null == message.getData()) {
|
||||
log.warn("Failure to parse external alert message. message: {}", externAlert.getMessage());
|
||||
return null;
|
||||
}
|
||||
// Note: Empty and false are both recovery notifications.
|
||||
// Note: There are no recovery notifications for event types
|
||||
boolean isAlarm = null != message.getData().getAlarm() && message.getData().getAlarm();
|
||||
Long alarmTime = DateUtil.getZonedTimeStampFromFormat(message.getData().getAlarmTime(), "yyyy/MM/dd HH:mm:ss 'GMT'XXX");
|
||||
return SingleAlert.builder()
|
||||
.triggerTimes(1)
|
||||
.status(isAlarm ? CommonConstants.ALERT_STATUS_FIRING : CommonConstants.ALERT_STATUS_RESOLVED)
|
||||
.startAt(alarmTime)
|
||||
.activeAt(Instant.now().toEpochMilli())
|
||||
.endAt(isAlarm ? null : alarmTime)
|
||||
.labels(buildLabels(message.getData()))
|
||||
.annotations(buildAnnotations(message.getData()))
|
||||
.content(formatContent(externAlert.getSubject(), message.getData()))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build basic annotations and fill annotations for huawei cloud.
|
||||
*
|
||||
* @param alertData alert content entity
|
||||
* @return annotations
|
||||
*/
|
||||
private Map<String, String> buildAnnotations(HuaweiCloudExternAlert.AlertData alertData) {
|
||||
Map<String, String> annotations = new HashMap<>(8);
|
||||
if (null != alertData) {
|
||||
putIfNotBlank(annotations, "region", alertData.getRegion());
|
||||
putIfNotBlank(annotations, "dimensionName", alertData.getDimensionName());
|
||||
putIfNotBlank(annotations, "resourceName", alertData.getResourceName());
|
||||
putIfNotBlank(annotations, "alarmRecordId", alertData.getAlarmRecordId());
|
||||
}
|
||||
return annotations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build basic labels and fill labels for huawei cloud.
|
||||
*
|
||||
* @param alertData alert content entity
|
||||
* @return labels
|
||||
*/
|
||||
private Map<String, String> buildLabels(HuaweiCloudExternAlert.AlertData alertData) {
|
||||
Map<String, String> labels = new HashMap<>(8);
|
||||
labels.put("__source__", "huaweicloud-ces");
|
||||
if (null != alertData) {
|
||||
putIfNotBlank(labels, "namespace", alertData.getNamespace());
|
||||
putIfNotBlank(labels, "metricName", alertData.getMetricName());
|
||||
putIfNotBlank(labels, "resourceId", alertData.getResourceId());
|
||||
putIfNotBlank(labels, "level", alertData.getAlarmLevel());
|
||||
}
|
||||
return labels;
|
||||
}
|
||||
|
||||
/**
|
||||
* todo i18n
|
||||
*
|
||||
* @param subject alert subject
|
||||
* @param alertData alert content entity
|
||||
* @return content
|
||||
*/
|
||||
private String formatContent(String subject, HuaweiCloudExternAlert.AlertData alertData) {
|
||||
if (null == alertData) {
|
||||
return subject;
|
||||
}
|
||||
return MessageFormat.format(
|
||||
"{0} threshold:{1}{2}, current:{3}",
|
||||
subject,
|
||||
alertData.getComparisonOperator(),
|
||||
alertData.getValue(),
|
||||
alertData.getCurrentData()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatic subscription url.
|
||||
*
|
||||
* @param subscribeUrl subscribeUrl
|
||||
*/
|
||||
public void autoSubscribeForUrl(String subscribeUrl) {
|
||||
if (StringUtils.isBlank(subscribeUrl)) {
|
||||
return;
|
||||
}
|
||||
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
|
||||
HttpGet httpGet = new HttpGet(subscribeUrl);
|
||||
try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
String responseBody = EntityUtils.toString(response.getEntity());
|
||||
|
||||
if (statusCode != 200) {
|
||||
log.error("Subscribe url request failed with status code: " + statusCode + ", response: " + responseBody);
|
||||
return;
|
||||
}
|
||||
JsonNode jsonResponse = JsonUtil.fromJson(responseBody);
|
||||
if (jsonResponse == null) {
|
||||
throw new IgnoreException("Subscribe url failed with status code: " + statusCode + ", response: " + responseBody);
|
||||
}
|
||||
JsonNode surnNode = jsonResponse.get("subscription_urn");
|
||||
if (surnNode == null || StringUtils.isBlank(surnNode.asText())) {
|
||||
throw new IgnoreException("Subscribe url failed with status code: " + statusCode + ", response: " + responseBody);
|
||||
}
|
||||
log.info("Successfully subscribed to Huawei Cloud(SMN) url.");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to subscribe url request: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifying the signature of huawei cloud alert message.
|
||||
*
|
||||
* @param externAlert alert content entity
|
||||
* @return verification result
|
||||
* @throws SecurityException thrown when validation fails
|
||||
*/
|
||||
private boolean isMessageValid(HuaweiCloudExternAlert externAlert) {
|
||||
try {
|
||||
String signMessage = buildSignMessage(externAlert);
|
||||
if (StringUtils.isBlank(signMessage)) {
|
||||
throw new SecurityException("Verify sign message is null");
|
||||
}
|
||||
X509Certificate cert = getCertificate(externAlert.getSigningCertUrl());
|
||||
return verifySignature(signMessage, cert, externAlert.getSignature());
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to verify message signature: ", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build sign message.
|
||||
*
|
||||
* @param externAlert alert content entity
|
||||
* @return sign message
|
||||
*/
|
||||
private String buildSignMessage(HuaweiCloudExternAlert externAlert) {
|
||||
if (NOTIFICATION.getType().equals(externAlert.getType())) {
|
||||
return buildNotificationMessage(externAlert);
|
||||
} else if (SUBSCRIPTION.getType().equals(externAlert.getType()) || UNSUBSCRIBE.getType().equals(externAlert.getType())){
|
||||
return buildSubscriptionMessage(externAlert);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Building sign message of 'Notification' type
|
||||
*
|
||||
* @param externAlert alert content entity
|
||||
* @return sign message
|
||||
*/
|
||||
private String buildNotificationMessage(HuaweiCloudExternAlert externAlert) {
|
||||
StringBuilder message = new StringBuilder();
|
||||
appendField(message, FIELD_MESSAGE, externAlert.getMessage());
|
||||
appendField(message, FIELD_MESSAGE_ID, externAlert.getMessageId());
|
||||
if (StringUtils.isNotBlank(externAlert.getSubject())) {
|
||||
appendField(message, FIELD_SUBJECT, externAlert.getSubject());
|
||||
}
|
||||
appendField(message, FIELD_TIMESTAMP, externAlert.getTimestamp());
|
||||
appendField(message, FIELD_TOPIC_URN, externAlert.getTopicUrn());
|
||||
appendField(message, FIELD_TYPE, externAlert.getType());
|
||||
return message.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Building sign message of 'SubscriptionConfirmation' or 'UnsubscribeConfirmation' type
|
||||
*
|
||||
* @param externAlert alert content entity
|
||||
* @return sign message
|
||||
*/
|
||||
private String buildSubscriptionMessage(HuaweiCloudExternAlert externAlert) {
|
||||
StringBuilder message = new StringBuilder();
|
||||
appendField(message, FIELD_MESSAGE, externAlert.getMessage());
|
||||
appendField(message, FIELD_MESSAGE_ID, externAlert.getMessageId());
|
||||
appendField(message, FIELD_SUBSCRIBE_URL, externAlert.getSubscribeUrl());
|
||||
appendField(message, FIELD_TIMESTAMP, externAlert.getTimestamp());
|
||||
appendField(message, FIELD_TOPIC_URN, externAlert.getTopicUrn());
|
||||
appendField(message, FIELD_TYPE, externAlert.getType());
|
||||
return message.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain certificate
|
||||
*
|
||||
* @param signCertUrl sign cert url
|
||||
* @return X509 certificate
|
||||
* @throws Exception Thrown when certificate acquisition fails
|
||||
*/
|
||||
private X509Certificate getCertificate(String signCertUrl) throws Exception {
|
||||
URL url = new URL(signCertUrl);
|
||||
try (InputStream in = url.openStream()) {
|
||||
CertificateFactory cf = CertificateFactory.getInstance(CERTIFICATE_TYPE);
|
||||
return (X509Certificate) cf.generateCertificate(in);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify signature
|
||||
*
|
||||
* @param message sign message
|
||||
* @param cert cert
|
||||
* @param signature signature
|
||||
* @return verification result
|
||||
* @throws Exception thrown when an error occurs in the validation process
|
||||
*/
|
||||
private boolean verifySignature(String message, X509Certificate cert, String signature) throws Exception {
|
||||
Signature sig = Signature.getInstance(cert.getSigAlgName());
|
||||
sig.initVerify(cert.getPublicKey());
|
||||
sig.update(message.getBytes(CHARSET_UTF8));
|
||||
return sig.verify(Base64.getDecoder().decode(signature));
|
||||
}
|
||||
|
||||
private void putIfNotBlank(Map<String, String> map, String key, String value) {
|
||||
if (StringUtils.isNotBlank(value)){
|
||||
map.put(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
private void appendField(StringBuilder builder, String fieldName, String value) {
|
||||
builder.append(fieldName).append("\n").append(value).append("\n");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String supportSource() {
|
||||
return "huaweicloud-ces";
|
||||
}
|
||||
|
||||
}
|
||||
+244
@@ -0,0 +1,244 @@
|
||||
/*
|
||||
* 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.service.impl;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.hertzbeat.alert.dto.VolcEngineExternEventAlert;
|
||||
import org.apache.hertzbeat.alert.dto.VolcEngineExternMetricAlert;
|
||||
import org.apache.hertzbeat.alert.reduce.AlarmCommonReduce;
|
||||
import org.apache.hertzbeat.alert.service.ExternAlertService;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Volcengine alarm entity class
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class VolcEngineExternAlertService implements ExternAlertService {
|
||||
|
||||
private final AlarmCommonReduce alarmCommonReduce;
|
||||
private static final Map<String, Integer> severityOrder = Map.of(
|
||||
CommonConstants.ALERT_SEVERITY_CRITICAL, 1,
|
||||
CommonConstants.ALERT_SEVERITY_WARNING, 2,
|
||||
CommonConstants.ALERT_SEVERITY_INFO, 3
|
||||
);
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void addExternAlert(String content) {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
JsonNode root = mapper.readTree(content);
|
||||
String type = root.get("Type").asText();
|
||||
if (VolcEngineExternMetricAlert.ALERT_TYPE_EVENT.equals(type)) {
|
||||
VolcEngineExternEventAlert eventAlert = JsonUtil.fromJson(content, VolcEngineExternEventAlert.class);
|
||||
if (eventAlert == null) {
|
||||
log.warn("parse extern event alert content failed! content: {}", content);
|
||||
return;
|
||||
}
|
||||
SingleAlert singleAlert = new VolcEngineAlertConverter().convertEventToSingleAlert(eventAlert);
|
||||
alarmCommonReduce.reduceAndSendAlarm(singleAlert);
|
||||
|
||||
} else {
|
||||
// deal with metric alert
|
||||
VolcEngineExternMetricAlert report = JsonUtil.fromJson(content, VolcEngineExternMetricAlert.class);
|
||||
if (report == null) {
|
||||
log.warn("parse extern metrics alert content failed! content: {}", content);
|
||||
return;
|
||||
}
|
||||
for (SingleAlert singleAlert : new VolcEngineAlertConverter().convertMetricAlertToSingeAlert(report)) {
|
||||
alarmCommonReduce.reduceAndSendAlarm(singleAlert);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String supportSource() {
|
||||
return "volcengine";
|
||||
}
|
||||
|
||||
/**
|
||||
* Converter: VolcEngine alert to SingleAlert
|
||||
*/
|
||||
public static class VolcEngineAlertConverter {
|
||||
|
||||
/**
|
||||
* Convert VolcEngine metric alert to SingleAlert List
|
||||
*/
|
||||
public List<SingleAlert> convertMetricAlertToSingeAlert(VolcEngineExternMetricAlert alert) {
|
||||
String status = convertStatus(alert);
|
||||
if (Objects.equals(alert.getType(), VolcEngineExternMetricAlert.ALERT_TYPE_METRIC)) {
|
||||
return convertMetricAlert(alert, status, alert.getResources());
|
||||
}
|
||||
if (Objects.equals(alert.getType(), VolcEngineExternMetricAlert.ALERT_TYPE_METRICS_NODATA)) {
|
||||
return convertMetricAlert(alert, status, alert.getNoDataResources());
|
||||
}
|
||||
if (Objects.equals(alert.getType(), VolcEngineExternMetricAlert.ALERT_TYPE_NO_DATA_RECOVERED)) {
|
||||
return convertMetricAlert(alert, status, alert.getNoDataRecoveredResources());
|
||||
}
|
||||
if (Objects.equals(alert.getType(), VolcEngineExternMetricAlert.ALERT_TYPE_METRIC_RECOVERED)) {
|
||||
return convertMetricAlert(alert, status, alert.getRecoveredResources());
|
||||
}
|
||||
return List.of();
|
||||
}
|
||||
|
||||
public SingleAlert convertEventToSingleAlert(VolcEngineExternEventAlert alert) {
|
||||
SingleAlert.SingleAlertBuilder builder = SingleAlert.builder()
|
||||
.status(CommonConstants.ALERT_STATUS_FIRING)
|
||||
.startAt(alert.getHappenedAt() * 1000)
|
||||
.activeAt(alert.getHappenedAt() * 1000)
|
||||
.labels(buildEventLabels(alert))
|
||||
.content(alert.getDescriptionCn())
|
||||
.annotations(new HashMap<>());
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* build labels for volcengine event alert
|
||||
*/
|
||||
private Map<String, String> buildEventLabels(VolcEngineExternEventAlert event) {
|
||||
Map<String, String> labels = new HashMap<>();
|
||||
labels.put("severity", convertEventSeverity(event));
|
||||
labels.put("__source__", "volcengine");
|
||||
labels.put("resource_name", event.getDetails().getVolcResourceName());
|
||||
labels.put("account_id", event.getAccountId());
|
||||
labels.put("region", event.getRegion());
|
||||
labels.put("event_type", event.getEventType());
|
||||
labels.put("source", event.getSource());
|
||||
return labels;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* convert volcengine event alert status to heartbeat alert status
|
||||
* use the most severe level in the rules
|
||||
*
|
||||
* @param alert volcengine event alert
|
||||
* @return status
|
||||
*/
|
||||
private String convertEventSeverity(VolcEngineExternEventAlert alert) {
|
||||
return alert.getRules().stream().map(VolcEngineExternEventAlert.EventRule::getLevel)
|
||||
.min((o1, o2) -> severityOrder.get(convertCommonSeverity(o1)).compareTo(severityOrder.get(convertCommonSeverity(o2))))
|
||||
.orElse(CommonConstants.ALERT_SEVERITY_INFO);
|
||||
}
|
||||
|
||||
private String convertCommonSeverity(String level) {
|
||||
return switch (level) {
|
||||
case "critical" -> CommonConstants.ALERT_SEVERITY_CRITICAL;
|
||||
case "warning" -> CommonConstants.ALERT_SEVERITY_WARNING;
|
||||
default -> CommonConstants.ALERT_SEVERITY_INFO;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* create SingleAlert for each resources in volcengine alert
|
||||
*
|
||||
* @param alert volcengine alert
|
||||
* @param status status
|
||||
* @param resources resources
|
||||
* @return List of SingleAlert
|
||||
*/
|
||||
private List<SingleAlert> convertMetricAlert(VolcEngineExternMetricAlert alert, String status,
|
||||
List<? extends VolcEngineExternMetricAlert.Resource> resources) {
|
||||
if (CollectionUtils.isEmpty(resources)) {
|
||||
return List.of();
|
||||
}
|
||||
List<SingleAlert> result = new ArrayList<>();
|
||||
for (VolcEngineExternMetricAlert.Resource resource : resources) {
|
||||
SingleAlert.SingleAlertBuilder builder = SingleAlert.builder()
|
||||
.status(status)
|
||||
.startAt(resource.getFirstAlertTime() * 1000)
|
||||
.endAt(resource.getLastAlertTime() * 1000)
|
||||
.labels(buildLabels(alert, resource))
|
||||
.activeAt(convertHappenAt(alert.getHappenedAt()))
|
||||
.content(resource.getName() + alert.getRuleCondition())
|
||||
.annotations(buildAnnotations(resource));
|
||||
result.add(builder.build());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Long convertHappenAt(String happenAt) {
|
||||
String cleanedStr = happenAt.replace("UTC", "").replace("(", "").replace(")", "");
|
||||
DateTimeFormatter altFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ssXXX");
|
||||
OffsetDateTime odt = OffsetDateTime.parse(cleanedStr, altFormatter);
|
||||
return odt.toEpochSecond() * 1000;
|
||||
}
|
||||
|
||||
private Map<String, String> buildLabels(VolcEngineExternMetricAlert alert, VolcEngineExternMetricAlert.Resource resource) {
|
||||
Map<String, String> labels = new HashMap<>();
|
||||
labels.put("__source__", "volcengine");
|
||||
labels.put("resource_name", resource.getName());
|
||||
labels.put("resource_id", resource.getId());
|
||||
labels.put("rule_id", alert.getRuleId());
|
||||
return labels;
|
||||
}
|
||||
|
||||
private Map<String, String> buildAnnotations(VolcEngineExternMetricAlert.Resource resource) {
|
||||
Map<String, String> annotations = new HashMap<>();
|
||||
if (resource instanceof VolcEngineExternMetricAlert.NoDataResource noDataResource) {
|
||||
for (VolcEngineExternMetricAlert.Metric noDataMetric : noDataResource.getNoDataMetrics()) {
|
||||
annotations.put(noDataMetric.getName(), "N/A");
|
||||
}
|
||||
}
|
||||
for (VolcEngineExternMetricAlert.Metric metric : resource.getMetrics()) {
|
||||
annotations.put(metric.getName(), metric.getCurrentValue() + metric.getUnit());
|
||||
}
|
||||
annotations.put("region", resource.getRegion());
|
||||
List<VolcEngineExternMetricAlert.Dimension> dimensions = resource.getDimensions();
|
||||
if (CollectionUtils.isNotEmpty(dimensions)) {
|
||||
for (VolcEngineExternMetricAlert.Dimension dimension : dimensions) {
|
||||
annotations.put(dimension.getNameCn(), dimension.getValue());
|
||||
}
|
||||
}
|
||||
return annotations;
|
||||
}
|
||||
|
||||
/**
|
||||
* convert volcengine alert status to heartbeat alert status
|
||||
*
|
||||
* @param alert volcengine alert
|
||||
* @return status
|
||||
*/
|
||||
private String convertStatus(VolcEngineExternMetricAlert alert) {
|
||||
String type = alert.getType();
|
||||
if (Objects.equals(type, VolcEngineExternMetricAlert.ALERT_TYPE_METRIC)
|
||||
|| Objects.equals(type, VolcEngineExternMetricAlert.ALERT_TYPE_EVENT)
|
||||
|| Objects.equals(type, VolcEngineExternMetricAlert.ALERT_TYPE_METRICS_NODATA)) {
|
||||
return CommonConstants.ALERT_STATUS_FIRING;
|
||||
}
|
||||
return CommonConstants.ALERT_STATUS_RESOLVED;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ package org.apache.hertzbeat.alert.util;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeFormatterBuilder;
|
||||
import java.util.Optional;
|
||||
@@ -80,4 +81,19 @@ public final class DateUtil {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* convert format data to timestamp
|
||||
*/
|
||||
public static Long getZonedTimeStampFromFormat(String dateStr, String format) {
|
||||
try {
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);
|
||||
// the parsed zoned date-time, not null
|
||||
return ZonedDateTime.parse(dateStr, formatter).toInstant().toEpochMilli();
|
||||
} catch (Exception e) {
|
||||
log.error("Error parsing date '{}' with format '{}': {}",
|
||||
dateStr, format, e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -32,3 +32,4 @@ alerter.notify.console = Console Login
|
||||
alerter.priority.0 = Emergency Alert
|
||||
alerter.priority.1 = Critical Alert
|
||||
alerter.priority.2 = Warning Alert
|
||||
alerter.calculate.parse.error = Expression is not fully parsed, may have syntax errors or incomplete inputs
|
||||
|
||||
@@ -32,3 +32,4 @@ alerter.notify.console = 登入控制台
|
||||
alerter.priority.0 = 紧急告警
|
||||
alerter.priority.1 = 严重告警
|
||||
alerter.priority.2 = 警告告警
|
||||
alerter.calculate.parse.error = 表达式未完全解析,可能存在语法错误或输入不完整
|
||||
|
||||
@@ -32,3 +32,4 @@ alerter.notify.console = 控制台登錄
|
||||
alerter.priority.0 = 緊急警報
|
||||
alerter.priority.1 = 嚴重警報
|
||||
alerter.priority.2 = 警告警報
|
||||
alerter.calculate.parse.error = 表達式未完全解析,可能存在語法錯誤或輸入不完整
|
||||
|
||||
@@ -22,36 +22,272 @@ expression
|
||||
;
|
||||
|
||||
expr
|
||||
: '(' expr ')' # ParenExpr
|
||||
| left=expr op=('>='|'<='|'>'|'<'|'=='|'!=') right=expr # ComparisonExpr
|
||||
| left=expr 'and' right=expr # AndExpr
|
||||
| left=expr 'unless' right=expr # UnlessExpr
|
||||
| left=expr 'or' right=expr # OrExpr
|
||||
| identifier # QueryExpr
|
||||
| number # LiteralExpr
|
||||
: LPAREN expr RPAREN # ParenExpr
|
||||
| left=expr op=(GE|LE|GT|LT|EQ|NE) right=expr # ComparisonExpr
|
||||
| left=expr AND right=expr # AndExpr
|
||||
| left=expr UNLESS right=expr # UnlessExpr
|
||||
| left=expr OR right=expr # OrExpr
|
||||
| promql # PromqlExpr
|
||||
| selectSql # SqlExpr
|
||||
| number # LiteralExpr
|
||||
| SQL_FUNCTION LPAREN string RPAREN # SqlCallExpr
|
||||
| PROMQL_FUNCTION LPAREN string RPAREN # PromqlCallExpr
|
||||
;
|
||||
|
||||
// Lexer rules
|
||||
AND : 'and' ;
|
||||
OR : 'or' ;
|
||||
UNLESS : 'unless' ;
|
||||
GT : '>' ;
|
||||
GE : '>=' ;
|
||||
LT : '<' ;
|
||||
LE : '<=' ;
|
||||
EQ : '==' ;
|
||||
NE : '!=' ;
|
||||
LPAREN : '(' ;
|
||||
RPAREN : ')' ;
|
||||
functionCall
|
||||
: functionName LPAREN parameterList RPAREN
|
||||
;
|
||||
|
||||
identifier
|
||||
: IDENTIFIER
|
||||
parameterList
|
||||
: parameter (COMMA parameter)*
|
||||
;
|
||||
|
||||
parameter
|
||||
: expr
|
||||
| STAR
|
||||
| string
|
||||
| duration
|
||||
;
|
||||
|
||||
number
|
||||
: NUMBER
|
||||
| FLOAT
|
||||
| SCIENTIFIC_NUMBER
|
||||
;
|
||||
|
||||
IDENTIFIER : [a-zA-Z_] [a-zA-Z0-9_=~{}[\]".]*;
|
||||
NUMBER : [0-9]+ ('.' [0-9]+)? ;
|
||||
WS : [ \t\r\n]+ -> skip ;
|
||||
string
|
||||
: STRING
|
||||
;
|
||||
|
||||
duration
|
||||
: DURATION
|
||||
;
|
||||
|
||||
functionName
|
||||
: COUNT
|
||||
| AVG
|
||||
| SUM
|
||||
| MIN
|
||||
| MAX
|
||||
| RATE_FUNCTION
|
||||
| INCREASE_FUNCTION
|
||||
| HISTOGRAM_QUANTILE_FUNCTION
|
||||
| BY_FUNCTION
|
||||
| WITHOUT_FUNCTION
|
||||
| GROUP_LEFT_FUNCTION
|
||||
| GROUP_RIGHT_FUNCTION
|
||||
| IGNORING_FUNCTION
|
||||
| ON_FUNCTION
|
||||
| IDENTIFIER
|
||||
;
|
||||
|
||||
// SQL grammar for complex queries
|
||||
selectSql
|
||||
: SELECT selectFieldList FROM relList (WHERE conditionList)?
|
||||
(GROUP BY groupByList)? (HAVING conditionList)?
|
||||
(ORDER BY orderByList)? (LIMIT limitClause)?
|
||||
;
|
||||
|
||||
selectFieldList
|
||||
: selectField (COMMA selectField)*
|
||||
;
|
||||
|
||||
selectField
|
||||
: functionCall (AS? IDENTIFIER)?
|
||||
| IDENTIFIER (AS? IDENTIFIER)?
|
||||
| STAR (AS? IDENTIFIER)?
|
||||
| IDENTIFIER DOT IDENTIFIER (AS? IDENTIFIER)?
|
||||
;
|
||||
|
||||
groupByList
|
||||
: IDENTIFIER (COMMA IDENTIFIER)*
|
||||
;
|
||||
|
||||
orderByList
|
||||
: orderByField (COMMA orderByField)*
|
||||
;
|
||||
|
||||
orderByField
|
||||
: IDENTIFIER (ASC | DESC)?
|
||||
| functionCall (ASC | DESC)?
|
||||
;
|
||||
|
||||
limitClause
|
||||
: NUMBER
|
||||
;
|
||||
|
||||
relList
|
||||
: relation (COMMA relation)*
|
||||
;
|
||||
|
||||
relation
|
||||
: IDENTIFIER (AS? IDENTIFIER)?
|
||||
| LPAREN selectSql RPAREN AS IDENTIFIER
|
||||
;
|
||||
|
||||
conditionList
|
||||
: conditionList AND conditionList
|
||||
| conditionList OR conditionList
|
||||
| condition
|
||||
| LPAREN conditionList RPAREN
|
||||
;
|
||||
|
||||
compOp
|
||||
: EQ | LT | GT | LE | GE | NE | LIKE | NOT LIKE | IN | NOT IN | IS | IS NOT
|
||||
;
|
||||
|
||||
condition
|
||||
: conditionUnit compOp conditionUnit
|
||||
| LPAREN condition RPAREN
|
||||
| IDENTIFIER BETWEEN number AND number
|
||||
;
|
||||
|
||||
conditionUnit
|
||||
: number
|
||||
| string
|
||||
| IDENTIFIER
|
||||
| IDENTIFIER DOT IDENTIFIER
|
||||
| NULL
|
||||
| LPAREN selectSql RPAREN
|
||||
| functionCall
|
||||
;
|
||||
|
||||
// PromQL query expressions
|
||||
promql
|
||||
: metricSelector instantVectorOp?
|
||||
| aggregationOperator LPAREN promql (BY labelList)? RPAREN
|
||||
| functionCall
|
||||
| promql LBRACKET duration RBRACKET
|
||||
| promql LBRACKET duration COLON duration RBRACKET
|
||||
| promql OFFSET duration
|
||||
| IDENTIFIER
|
||||
;
|
||||
|
||||
metricSelector
|
||||
: LBRACE labelMatcherList? RBRACE
|
||||
;
|
||||
|
||||
labelMatcherList
|
||||
: labelMatcherItem (COMMA labelMatcherItem)*
|
||||
;
|
||||
|
||||
labelMatcherItem
|
||||
: IDENTIFIER labelMatcherOp string
|
||||
;
|
||||
|
||||
labelMatcherOp
|
||||
: EQ | NE
|
||||
;
|
||||
|
||||
labelList
|
||||
: LPAREN IDENTIFIER (COMMA IDENTIFIER)* RPAREN
|
||||
;
|
||||
|
||||
instantVectorOp
|
||||
: LBRACKET duration RBRACKET
|
||||
;
|
||||
|
||||
aggregationOperator
|
||||
: SUM | AVG | COUNT | MIN | MAX | STDDEV | STDVAR | TOPK | BOTTOMK | QUANTILE
|
||||
;
|
||||
|
||||
binaryOperator
|
||||
: EQ | NE | GT | LT | GE | LE
|
||||
| AND | OR | UNLESS
|
||||
;
|
||||
|
||||
// Lexer rules
|
||||
|
||||
// Boolean operators
|
||||
AND : [Aa][Nn][Dd] ;
|
||||
OR : [Oo][Rr] ;
|
||||
UNLESS : [Uu][Nn][Ll][Ee][Ss][Ss] ;
|
||||
NOT : [Nn][Oo][Tt] ;
|
||||
|
||||
// SQL keywords
|
||||
SELECT : [Ss][Ee][Ll][Ee][Cc][Tt] ;
|
||||
FROM : [Ff][Rr][Oo][Mm] ;
|
||||
WHERE : [Ww][Hh][Ee][Rr][Ee] ;
|
||||
GROUP : [Gg][Rr][Oo][Uu][Pp] ;
|
||||
BY : [Bb][Yy] ;
|
||||
HAVING : [Hh][Aa][Vv][Ii][Nn][Gg] ;
|
||||
ORDER : [Oo][Rr][Dd][Ee][Rr] ;
|
||||
LIMIT : [Ll][Ii][Mm][Ii][Tt] ;
|
||||
OFFSET : [Oo][Ff][Ff][Ss][Ee][Tt] ;
|
||||
AS : [Aa][Ss] ;
|
||||
ASC : [Aa][Ss][Cc] ;
|
||||
DESC : [Dd][Ee][Ss][Cc] ;
|
||||
IN : [Ii][Nn] ;
|
||||
IS : [Ii][Ss] ;
|
||||
NULL : [Nn][Uu][Ll][Ll] ;
|
||||
LIKE : [Ll][Ii][Kk][Ee] ;
|
||||
BETWEEN : [Bb][Ee][Tt][Ww][Ee][Ee][Nn] ;
|
||||
STAR : '*' ;
|
||||
|
||||
// Aggregate functions
|
||||
COUNT : [Cc][Oo][Uu][Nn][Tt] ;
|
||||
SUM : [Ss][Uu][Mm] ;
|
||||
AVG : [Aa][Vv][Gg] ;
|
||||
MIN : [Mm][Ii][Nn] ;
|
||||
MAX : [Mm][Aa][Xx] ;
|
||||
STDDEV : [Ss][Tt][Dd][Dd][Ee][Vv] ;
|
||||
STDVAR : [Ss][Tt][Dd][Vv][Aa][Rr] ;
|
||||
VARIANCE: [Vv][Aa][Rr][Ii][Aa][Nn][Cc][Ee] ;
|
||||
|
||||
// PromQL specific functions
|
||||
RATE_FUNCTION : [Rr][Aa][Tt][Ee] ;
|
||||
INCREASE_FUNCTION: [Ii][Nn][Cc][Rr][Ee][Aa][Ss][Ee] ;
|
||||
HISTOGRAM_QUANTILE_FUNCTION: [Hh][Ii][Ss][Tt][Oo][Gg][Rr][Aa][Mm] '_' [Qq][Uu][Aa][Nn][Tt][Ii][Ll][Ee] ;
|
||||
TOPK : [Tt][Oo][Pp][Kk] ;
|
||||
BOTTOMK : [Bb][Oo][Tt][Tt][Oo][Mm][Kk] ;
|
||||
QUANTILE: [Qq][Uu][Aa][Nn][Tt][Ii][Ll][Ee] ;
|
||||
BY_FUNCTION: [Bb][Yy] ;
|
||||
WITHOUT_FUNCTION: [Ww][Ii][Tt][Hh][Oo][Uu][Tt] ;
|
||||
GROUP_LEFT_FUNCTION: [Gg][Rr][Oo][Uu][Pp] '_' [Ll][Ee][Ff][Tt] ;
|
||||
GROUP_RIGHT_FUNCTION: [Gg][Rr][Oo][Uu][Pp] '_' [Rr][Ii][Gg][Hh][Tt] ;
|
||||
IGNORING_FUNCTION: [Ii][Gg][Nn][Oo][Rr][Ii][Nn][Gg] ;
|
||||
ON_FUNCTION: [Oo][Nn] ;
|
||||
|
||||
// Other functions
|
||||
SQL_FUNCTION: [Ss][Qq][Ll] ;
|
||||
PROMQL_FUNCTION: [Pp][Rr][Oo][Mm][Qq][Ll] ;
|
||||
|
||||
// Comparison operators
|
||||
GT : '>' ;
|
||||
GE : '>=' ;
|
||||
LT : '<' ;
|
||||
LE : '<=' ;
|
||||
EQ : '==' | '=' ;
|
||||
NE : '!=' ;
|
||||
|
||||
// Delimiters
|
||||
LPAREN : '(' ;
|
||||
RPAREN : ')' ;
|
||||
LBRACE : '{' ;
|
||||
RBRACE : '}' ;
|
||||
LBRACKET: '[' ;
|
||||
RBRACKET: ']' ;
|
||||
COMMA : ',' ;
|
||||
DOT : '.' ;
|
||||
COLON : ':' ;
|
||||
SEMICOLON: ';' ;
|
||||
|
||||
// number formats
|
||||
SCIENTIFIC_NUMBER: [0-9]+ ('.' [0-9]+)? [eE] [+-]? [0-9]+ ;
|
||||
FLOAT : [0-9]+ '.' [0-9]+ ;
|
||||
NUMBER : [0-9]+ ;
|
||||
|
||||
// Duration literals for PromQL (e.g., 5m, 1h, 30s)
|
||||
DURATION : [0-9]+ [a-zA-Z]+ ;
|
||||
|
||||
// String literals
|
||||
STRING : '"' (~["\r\n\\] | '\\' .)* '"'
|
||||
| '\'' (~['\r\n\\] | '\\' .)* '\'' ;
|
||||
|
||||
// Identifiers and metric names
|
||||
IDENTIFIER : [a-zA-Z_] [a-zA-Z0-9_=~{}[\]".~-]* ;
|
||||
|
||||
// Whitespace and comments
|
||||
WS : [ \t\r\n]+ -> channel(HIDDEN) ;
|
||||
LINE_COMMENT : '//' ~[\r\n]* -> skip ;
|
||||
BLOCK_COMMENT : '/*' .*? '*/' -> skip ;
|
||||
+251
@@ -0,0 +1,251 @@
|
||||
/*
|
||||
* 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.calculate;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import org.apache.hertzbeat.alert.AlerterWorkerPool;
|
||||
import org.apache.hertzbeat.alert.dao.SingleAlertDao;
|
||||
import org.apache.hertzbeat.alert.reduce.AlarmCommonReduce;
|
||||
import org.apache.hertzbeat.alert.service.AlertDefineService;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.constants.MetricDataConstants;
|
||||
import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
|
||||
import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
import org.apache.hertzbeat.common.queue.CommonDataQueue;
|
||||
import org.apache.hertzbeat.common.queue.impl.InMemoryCommonDataQueue;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class RealTimeAlertCalculatorMatchTest {
|
||||
|
||||
private final AlerterWorkerPool workerPool = new AlerterWorkerPool();
|
||||
|
||||
@Mock
|
||||
private CommonDataQueue dataQueue = new InMemoryCommonDataQueue();
|
||||
|
||||
@Mock
|
||||
private AlertDefineService alertDefineService;
|
||||
|
||||
@Mock
|
||||
private SingleAlertDao singleAlertDao;
|
||||
|
||||
@Mock
|
||||
private AlarmCommonReduce alarmCommonReduce;
|
||||
|
||||
@Mock
|
||||
private AlarmCacheManager alarmCacheManager;
|
||||
|
||||
private RealTimeAlertCalculator realTimeAlertCalculator;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
when(singleAlertDao.querySingleAlertsByStatus(any())).thenReturn(new ArrayList<>());
|
||||
realTimeAlertCalculator = new RealTimeAlertCalculator(
|
||||
workerPool,
|
||||
dataQueue,
|
||||
alertDefineService,
|
||||
singleAlertDao,
|
||||
alarmCommonReduce,
|
||||
alarmCacheManager,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFilterThresholdsByAppAndMetrics_withInstanceExpr_HasSpace() {
|
||||
|
||||
String app = "redis";
|
||||
String instanceId = "501045327364864";
|
||||
int priority = 0;
|
||||
|
||||
AlertDefine matchDefine = new AlertDefine();
|
||||
matchDefine.setExpr("equals(__app__,\"redis\") && equals(__instance__, \"501045327364864\")");
|
||||
|
||||
AlertDefine unmatchDefine = new AlertDefine();
|
||||
unmatchDefine.setExpr("equals(__app__,\"redis\") && equals(__instance__, \"999999999\")");
|
||||
|
||||
List<AlertDefine> allDefines = Collections.singletonList(matchDefine);
|
||||
|
||||
List<AlertDefine> filtered = realTimeAlertCalculator.filterThresholdsByAppAndMetrics(allDefines, app, "", Map.of(), instanceId, priority);
|
||||
|
||||
// It should filter out 999999999.
|
||||
assertEquals(1, filtered.size());
|
||||
assertEquals("equals(__app__,\"redis\") && equals(__instance__, \"501045327364864\")",
|
||||
filtered.get(0).getExpr());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPrometheusReplaceMultipleJobsApp() throws InterruptedException {
|
||||
CollectRep.MetricsData.Builder builder = CollectRep.MetricsData.newBuilder();
|
||||
builder.setId(518789738974464L)
|
||||
.setApp("_prometheus_Cool_Stingray_34Nj_copy")
|
||||
.setMetrics("canal_instance")
|
||||
.setPriority(0)
|
||||
.setCode(CollectRep.Code.SUCCESS);
|
||||
|
||||
CollectRep.Field destination = CollectRep.Field.newBuilder().setName("destination").setType(CommonConstants.TYPE_STRING).setLabel(true).build();
|
||||
CollectRep.Field mode = CollectRep.Field.newBuilder().setName("mode").setType(CommonConstants.TYPE_STRING).setLabel(true).build();
|
||||
CollectRep.Field metricValue = CollectRep.Field.newBuilder().setName("metric_value").setType(CommonConstants.TYPE_NUMBER).setLabel(true).build();
|
||||
|
||||
Map<String, String> meta = new HashMap<>();
|
||||
meta.put(MetricDataConstants.INSTANCE_NAME, "Cool_Stingray_34Nj_copy");
|
||||
meta.put(MetricDataConstants.INSTANCE_HOST, "127.0.0.1");
|
||||
|
||||
builder.addMetadataAll(meta);
|
||||
builder.addAllFields(Lists.newArrayList(destination, mode, metricValue));
|
||||
builder.addValueRow(CollectRep.ValueRow.newBuilder().addColumn("example").addColumn("spring").addColumn("1.0").build());
|
||||
|
||||
CollectRep.MetricsData metricsData = builder.build();
|
||||
|
||||
|
||||
AlertDefine matchDefine = new AlertDefine();
|
||||
matchDefine.setName("test");
|
||||
matchDefine.setExpr(
|
||||
"equals(__app__,\"prometheus\") && "
|
||||
+ "equals(__metrics__,\"canal_instance\") && "
|
||||
+ "(equals(__instance__, \"515224274242816\") or equals(__instance__, \"518789738974464\")) && "
|
||||
+ "metric_value > 0"
|
||||
);
|
||||
matchDefine.setTemplate("Canal instance val: ${value}%");
|
||||
matchDefine.setTimes(1);
|
||||
|
||||
List<AlertDefine> allDefines = Collections.singletonList(matchDefine);
|
||||
|
||||
when(alertDefineService.getRealTimeAlertDefines()).thenReturn(allDefines);
|
||||
when(dataQueue.pollMetricsDataToAlerter()).thenReturn(metricsData).thenThrow(new InterruptedException());
|
||||
|
||||
realTimeAlertCalculator.startCalculate();
|
||||
|
||||
Thread.sleep(3000);
|
||||
|
||||
verify(alarmCacheManager, times(1)).getPending(any());
|
||||
verify(alarmCacheManager, times(1)).putFiring(any(), any());
|
||||
verify(alarmCommonReduce, times(1)).reduceAndSendAlarm(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPrometheusReplaceApp() throws InterruptedException {
|
||||
CollectRep.MetricsData.Builder builder = CollectRep.MetricsData.newBuilder();
|
||||
builder.setId(1)
|
||||
.setApp("_prometheus_Cool_Stingray_34Nj")
|
||||
.setMetrics("canal_instance")
|
||||
.setPriority(0)
|
||||
.setCode(CollectRep.Code.SUCCESS);
|
||||
|
||||
CollectRep.Field destination = CollectRep.Field.newBuilder().setName("destination").setType(CommonConstants.TYPE_STRING).setLabel(true).build();
|
||||
CollectRep.Field mode = CollectRep.Field.newBuilder().setName("mode").setType(CommonConstants.TYPE_STRING).setLabel(true).build();
|
||||
CollectRep.Field metricValue = CollectRep.Field.newBuilder().setName("metric_value").setType(CommonConstants.TYPE_NUMBER).setLabel(true).build();
|
||||
|
||||
Map<String, String> meta = new HashMap<>();
|
||||
meta.put(MetricDataConstants.INSTANCE_NAME, "Cool_Stingray_34Nj");
|
||||
meta.put(MetricDataConstants.INSTANCE_HOST, "127.0.0.1");
|
||||
|
||||
builder.addMetadataAll(meta);
|
||||
builder.addAllFields(Lists.newArrayList(destination, mode, metricValue));
|
||||
builder.addValueRow(CollectRep.ValueRow.newBuilder().addColumn("example").addColumn("spring").addColumn("1.0").build());
|
||||
|
||||
CollectRep.MetricsData metricsData = builder.build();
|
||||
|
||||
AlertDefine matchDefine = new AlertDefine();
|
||||
matchDefine.setName("test");
|
||||
matchDefine.setExpr("equals(__app__,\"prometheus\") && equals(__metrics__,\"canal_instance\") && metric_value > 0");
|
||||
matchDefine.setTemplate("Canal instance val: ${value}%");
|
||||
matchDefine.setTimes(1);
|
||||
|
||||
List<AlertDefine> allDefines = Collections.singletonList(matchDefine);
|
||||
|
||||
when(alertDefineService.getRealTimeAlertDefines()).thenReturn(allDefines);
|
||||
when(dataQueue.pollMetricsDataToAlerter()).thenReturn(metricsData).thenThrow(new InterruptedException());
|
||||
|
||||
realTimeAlertCalculator.startCalculate();
|
||||
|
||||
Thread.sleep(3000);
|
||||
|
||||
verify(alarmCacheManager, times(1)).getPending(any());
|
||||
verify(alarmCacheManager, times(1)).putFiring(any(), any());
|
||||
verify(alarmCommonReduce, times(1)).reduceAndSendAlarm(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCalculateWithNormalApp() throws InterruptedException {
|
||||
CollectRep.MetricsData.Builder builder = CollectRep.MetricsData.newBuilder();
|
||||
builder.setId(1)
|
||||
.setApp("springboot3")
|
||||
.setMetrics("available")
|
||||
.setPriority(0)
|
||||
.setCode(CollectRep.Code.SUCCESS)
|
||||
.setTenantId(0).setId(518679137103104L)
|
||||
.setTime(1749110170834L)
|
||||
.setPriority(0);
|
||||
|
||||
CollectRep.Field responseTime = CollectRep.Field.newBuilder()
|
||||
.setName("responseTime")
|
||||
.setType(CommonConstants.TYPE_STRING)
|
||||
.setUnit("ms")
|
||||
.setLabel(false)
|
||||
.build();
|
||||
|
||||
Map<String, String> meta = new HashMap<>();
|
||||
meta.put(MetricDataConstants.INSTANCE_NAME, "Vibrant_Gazelle_83vJ");
|
||||
meta.put(MetricDataConstants.INSTANCE_HOST, "127.0.0.1");
|
||||
|
||||
builder.addMetadataAll(meta);
|
||||
builder.addAllFields(Lists.newArrayList(responseTime));
|
||||
builder.addValueRow(CollectRep.ValueRow.newBuilder().addColumn("18").build());
|
||||
|
||||
CollectRep.MetricsData metricsData = builder.build();
|
||||
|
||||
AlertDefine matchDefine = new AlertDefine();
|
||||
matchDefine.setName("test");
|
||||
matchDefine.setExpr("equals(__app__,\"springboot3\") && equals(__metrics__,\"available\") && equals(__instance__, \"518679137103104\") && responseTime > 0");
|
||||
matchDefine.setTemplate("Canal instance val: ${value}%");
|
||||
matchDefine.setTimes(1);
|
||||
|
||||
List<AlertDefine> allDefines = Collections.singletonList(matchDefine);
|
||||
|
||||
when(alertDefineService.getRealTimeAlertDefines()).thenReturn(allDefines);
|
||||
when(dataQueue.pollMetricsDataToAlerter()).thenReturn(metricsData).thenThrow(new InterruptedException());
|
||||
|
||||
realTimeAlertCalculator.startCalculate();
|
||||
|
||||
Thread.sleep(3000);
|
||||
|
||||
verify(alarmCacheManager, times(1)).getPending(any());
|
||||
verify(alarmCacheManager, times(1)).putFiring(any(), any());
|
||||
verify(alarmCommonReduce, times(1)).reduceAndSendAlarm(any());
|
||||
}
|
||||
|
||||
}
|
||||
+38
@@ -17,15 +17,22 @@
|
||||
|
||||
package org.apache.hertzbeat.alert.controller;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.hertzbeat.alert.service.impl.AlertDefineServiceImpl;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
|
||||
import org.apache.hertzbeat.common.entity.alerter.AlertDefineMonitorBind;
|
||||
import org.apache.hertzbeat.common.entity.manager.Monitor;
|
||||
import org.apache.hertzbeat.common.support.exception.AlertExpressionException;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -100,6 +107,37 @@ class AlertDefineControllerTest {
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetDefinePreview() throws Exception {
|
||||
List<Map<String, Object>> previewData = new ArrayList<>();
|
||||
Map<String, Object> row = new HashMap<>();
|
||||
row.put("__value__", 123);
|
||||
row.put("job", "spring-boot");
|
||||
previewData.add(row);
|
||||
|
||||
Mockito.when(alertDefineService.getDefinePreview(anyString(), anyString(), anyString()))
|
||||
.thenReturn(previewData);
|
||||
|
||||
mockMvc.perform(MockMvcRequestBuilders.get("/api/alert/define/preview/{datasource}", "promql")
|
||||
.param("type", "periodic")
|
||||
.param("expr", "up == 1"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(0))
|
||||
.andExpect(jsonPath("$.data[0].__value__").value(123))
|
||||
.andExpect(jsonPath("$.data[0].job").value("spring-boot"));
|
||||
|
||||
Mockito.when(alertDefineService.getDefinePreview(anyString(), anyString(), anyString()))
|
||||
.thenThrow(new AlertExpressionException("Expression error"));
|
||||
|
||||
mockMvc.perform(MockMvcRequestBuilders.get("/api/alert/define/preview/{datasource}", "promql")
|
||||
.param("type", "periodic")
|
||||
.param("expr", "http_server_requests_seconds_count{!@~!!#$%^&}"))
|
||||
.andExpect(status().isBadRequest())
|
||||
.andExpect(jsonPath("$.code").exists())
|
||||
.andExpect(jsonPath("$.msg").value("Expression error"));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void modifyAlertDefine() throws Exception {
|
||||
mockMvc.perform(MockMvcRequestBuilders.put("/api/alert/define")
|
||||
|
||||
+596
-10
@@ -24,12 +24,14 @@ import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
@@ -38,76 +40,130 @@ import static org.mockito.Mockito.when;
|
||||
class AlertExpressionEvalVisitorTest {
|
||||
|
||||
private QueryExecutor mockExecutor;
|
||||
private AlertExpressionEvalVisitor visitor;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
mockExecutor = Mockito.mock(QueryExecutor.class);
|
||||
visitor = new AlertExpressionEvalVisitor(mockExecutor);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGreaterThan() {
|
||||
when(mockExecutor.execute("cpu")).thenReturn(List.of(new HashMap<>(Map.of("__value__", 80.0))));
|
||||
when(mockExecutor.execute("select cpu from cpu_table where id = 1")).thenReturn(
|
||||
List.of(new HashMap<>(Map.of("__value__", 80.0))));
|
||||
// promql
|
||||
List<Map<String, Object>> result = evaluate("cpu > 70");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(80.0, result.get(0).get("__value__"));
|
||||
//sql
|
||||
result = evaluate("(select cpu from cpu_table where id = 1) > 70");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(80.0, result.get(0).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGreaterThanWithInteger() {
|
||||
when(mockExecutor.execute("cpu")).thenReturn(List.of(new HashMap<>(Map.of("__value__", 80))));
|
||||
when(mockExecutor.execute("select cpu_usage from system_metrics where host = 'server1'")).thenReturn(
|
||||
List.of(new HashMap<>(Map.of("__value__", 80))));
|
||||
// promql
|
||||
List<Map<String, Object>> result = evaluate("cpu > 70");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(80, result.get(0).get("__value__"));
|
||||
// sql
|
||||
result = evaluate("(select cpu_usage from system_metrics where host = 'server1') > 70");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(80, result.get(0).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLessThan() {
|
||||
when(mockExecutor.execute("memory")).thenReturn(List.of(new HashMap<>(Map.of("__value__", 65.0))));
|
||||
when(mockExecutor.execute("select memory_usage from memory_table where instance = 'web1'")).thenReturn(
|
||||
List.of(new HashMap<>(Map.of("__value__", 65.0))));
|
||||
// promql
|
||||
List<Map<String, Object>> result = evaluate("memory < 70");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(65.0, result.get(0).get("__value__"));
|
||||
// sql
|
||||
result = evaluate("(select memory_usage from memory_table where instance = 'web1') < 70");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(65.0, result.get(0).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEqualWithTolerance() {
|
||||
when(mockExecutor.execute("disk")).thenReturn(List.of(new HashMap<>(Map.of("__value__", 99.999))));
|
||||
when(mockExecutor.execute("select disk_usage from storage_metrics where partition = '/'")).thenReturn(
|
||||
List.of(new HashMap<>(Map.of("__value__", 99.999))));
|
||||
// promql
|
||||
List<Map<String, Object>> result = evaluate("disk == 100");
|
||||
assertEquals(1, result.size());
|
||||
assertNull(result.get(0).get("__value__"));
|
||||
// sql
|
||||
result = evaluate("(select disk_usage from storage_metrics where partition = '/') == 100");
|
||||
assertEquals(1, result.size());
|
||||
assertNull(result.get(0).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNotEqual() {
|
||||
when(mockExecutor.execute("network")).thenReturn(List.of(new HashMap<>(Map.of("__value__", 50.0))));
|
||||
when(mockExecutor.execute("select bandwidth from network_stats where interface = 'eth0'")).thenReturn(
|
||||
List.of(new HashMap<>(Map.of("__value__", 50.0))));
|
||||
// promql
|
||||
List<Map<String, Object>> result = evaluate("network != 60");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(50.0, result.get(0).get("__value__"));
|
||||
// sql
|
||||
result = evaluate("(select bandwidth from network_stats where interface = 'eth0') != 60");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(50.0, result.get(0).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExactlyEqual() {
|
||||
when(mockExecutor.execute("threshold")).thenReturn(List.of(new HashMap<>(Map.of("__value__", 100.0))));
|
||||
when(mockExecutor.execute("select alert_threshold from alert_config where rule_id = 'cpu_high'")).thenReturn(
|
||||
List.of(new HashMap<>(Map.of("__value__", 100.0))));
|
||||
// promql
|
||||
List<Map<String, Object>> result = evaluate("threshold == 100");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(100.0, result.get(0).get("__value__"));
|
||||
// sql
|
||||
result = evaluate("(select alert_threshold from alert_config where rule_id = 'cpu_high') == 100");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(100.0, result.get(0).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMaxValueBoundary() {
|
||||
when(mockExecutor.execute("max_val")).thenReturn(List.of(new HashMap<>(Map.of("__value__", Double.MAX_VALUE))));
|
||||
when(mockExecutor.execute("select max_value from boundary_test where test_case = 'extreme'")).thenReturn(
|
||||
List.of(new HashMap<>(Map.of("__value__", Double.MAX_VALUE))));
|
||||
// promql
|
||||
List<Map<String, Object>> result = evaluate("max_val > 100");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(Double.MAX_VALUE, result.get(0).get("__value__"));
|
||||
// sql
|
||||
result = evaluate("(select max_value from boundary_test where test_case = 'extreme') > 100");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(Double.MAX_VALUE, result.get(0).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMinValueBoundary() {
|
||||
when(mockExecutor.execute("min_val")).thenReturn(List.of(new HashMap<>(Map.of("__value__", Double.MIN_VALUE))));
|
||||
when(mockExecutor.execute("select min_value from boundary_test where test_case = 'minimal'")).thenReturn(
|
||||
List.of(new HashMap<>(Map.of("__value__", Double.MIN_VALUE))));
|
||||
// promql
|
||||
List<Map<String, Object>> result = evaluate("min_val > 0");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(Double.MIN_VALUE, result.get(0).get("__value__"));
|
||||
// sql
|
||||
result = evaluate("(select min_value from boundary_test where test_case = 'minimal') > 0");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(Double.MIN_VALUE, result.get(0).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -121,25 +177,46 @@ class AlertExpressionEvalVisitorTest {
|
||||
@Test
|
||||
void testListValueWithMax() {
|
||||
when(mockExecutor.execute("multi_val")).thenReturn(List.of(new HashMap<>(Map.of("__value__", List.of(10.0, 20.0, 30.0)))));
|
||||
when(mockExecutor.execute("select values from multi_metrics where group_id = 'test_group'")).thenReturn(
|
||||
List.of(new HashMap<>(Map.of("__value__", List.of(10.0, 20.0, 30.0)))));
|
||||
// promql
|
||||
List<Map<String, Object>> result = evaluate("multi_val > 25");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(30.0, result.get(0).get("__value__"));
|
||||
// sql
|
||||
result = evaluate("(select values from multi_metrics where group_id = 'test_group') > 25");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(30.0, result.get(0).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testListValueWithMin() {
|
||||
when(mockExecutor.execute("multi_val")).thenReturn(List.of(new HashMap<>(Map.of("__value__", List.of(10.0, 20.0, 30.0)))));
|
||||
when(mockExecutor.execute("select response_times from performance_data where service = 'api'")).thenReturn(
|
||||
List.of(new HashMap<>(Map.of("__value__", List.of(10.0, 20.0, 30.0)))));
|
||||
// promql
|
||||
List<Map<String, Object>> result = evaluate("multi_val < 15");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(10.0, result.get(0).get("__value__"));
|
||||
// sql
|
||||
result = evaluate("(select response_times from performance_data where service = 'api') < 15");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(10.0, result.get(0).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEmptyListValue() {
|
||||
when(mockExecutor.execute("empty_list")).thenReturn(List.of(new HashMap<>(Map.of("__value__", List.of()))));
|
||||
when(mockExecutor.execute("select error_codes from error_log where date = '2024-01-01'")).thenReturn(
|
||||
List.of(new HashMap<>(Map.of("__value__", List.of()))));
|
||||
// promql
|
||||
List<Map<String, Object>> result = evaluate("empty_list > 50");
|
||||
assertEquals(1, result.size());
|
||||
assertNull(result.get(0).get("__value__"));
|
||||
// sql
|
||||
result = evaluate("(select error_codes from error_log where date = '2024-01-01') > 50");
|
||||
assertEquals(1, result.size());
|
||||
assertNull(result.get(0).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -147,19 +224,219 @@ class AlertExpressionEvalVisitorTest {
|
||||
when(mockExecutor.execute("a")).thenReturn(List.of(new HashMap<>(Map.of("__value__", 10.0))));
|
||||
when(mockExecutor.execute("b")).thenReturn(List.of(new HashMap<>(Map.of("__value__", 20.0))));
|
||||
when(mockExecutor.execute("c")).thenReturn(List.of(new HashMap<>(Map.of("__value__", 30.0))));
|
||||
when(mockExecutor.execute("select cpu from server_a where region = 'us-east'")).thenReturn(
|
||||
List.of(new HashMap<>(Map.of("__value__", 10.0))));
|
||||
when(mockExecutor.execute("select memory from server_b where region = 'us-west'")).thenReturn(
|
||||
List.of(new HashMap<>(Map.of("__value__", 20.0))));
|
||||
when(mockExecutor.execute("select disk from server_c where region = 'eu-central'")).thenReturn(
|
||||
List.of(new HashMap<>(Map.of("__value__", 30.0))));
|
||||
// promql
|
||||
List<Map<String, Object>> result = evaluate("(a > 5) and (b > 15 or c < 25)");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(10.0, result.get(0).get("__value__"));
|
||||
// sql
|
||||
result = evaluate("((select cpu from server_a where region = 'us-east') > 5)"
|
||||
+ " and ((select memory from server_b where region = 'us-west') > 15"
|
||||
+ " or (select disk from server_c where region = 'eu-central') < 25)");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(10.0, result.get(0).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMultipleUnlessConditions() {
|
||||
when(mockExecutor.execute("metric1")).thenReturn(List.of(new HashMap<>(Map.of("__value__", 40.0))));
|
||||
when(mockExecutor.execute("metric2")).thenReturn(List.of(new HashMap<>(Map.of("__value__", 50.0))));
|
||||
when(mockExecutor.execute("metric3")).thenReturn(List.of(new HashMap<>(Map.of("__value__", 60.0))));
|
||||
List<Map<String, Object>> result = evaluate("metric1 > 30 unless metric2 > 45 unless metric3 < 70");
|
||||
void testUnlessOpPromql() {
|
||||
String promql = "http_server_requests_seconds_count > 10 unless http_server_requests_seconds_max > 0";
|
||||
|
||||
Map<String, Object> countValue1 = new HashMap<>() {
|
||||
{
|
||||
put("exception", "none");
|
||||
put("instance", "host.docker.internal:8989");
|
||||
put("__value__", 1307);
|
||||
put("method", "GET");
|
||||
put("__name__", "http_server_requests_seconds_count");
|
||||
put("__timestamp__", "1.750320922467E9");
|
||||
put("error", "none");
|
||||
put("job", "spring-boot-app");
|
||||
put("uri", "/actuator/prometheus");
|
||||
put("outcome", "SUCCESS");
|
||||
put("status", "200");
|
||||
}
|
||||
};
|
||||
|
||||
Map<String, Object> maxValue1 = new HashMap<>() {
|
||||
{
|
||||
put("exception", "none");
|
||||
put("instance", "host.docker.internal:8989");
|
||||
put("__value__", 10.007799125);
|
||||
put("method", "GET");
|
||||
put("__name__", "http_server_requests_seconds_max");
|
||||
put("__timestamp__", "1.750320922467E9");
|
||||
put("error", "none");
|
||||
put("job", "spring-boot-app");
|
||||
put("uri", "/actuator/health");
|
||||
put("outcome", "SUCCESS");
|
||||
put("status", "200");
|
||||
}
|
||||
};
|
||||
|
||||
when(mockExecutor.execute("http_server_requests_seconds_count")).thenReturn(List.of(countValue1));
|
||||
when(mockExecutor.execute("http_server_requests_seconds_max")).thenReturn(List.of(maxValue1));
|
||||
List<Map<String, Object>> result = evaluate(promql);
|
||||
assertEquals(1, result.size());
|
||||
assertNull(result.get(0).get("__value__"));
|
||||
assertEquals(1307, result.get(0).get("__value__"));
|
||||
|
||||
maxValue1.put("uri", "/actuator/prometheus");
|
||||
result = evaluate(promql);
|
||||
assertEquals(0, result.size());
|
||||
|
||||
Map<String, Object> sumValue1 = new HashMap<>() {
|
||||
{
|
||||
put("exception", "none");
|
||||
put("instance", "host.docker.internal:8989");
|
||||
put("__value__", 20.018);
|
||||
put("method", "GET");
|
||||
put("__name__", "http_server_requests_seconds_sum");
|
||||
put("__timestamp__", "1.750320922467E9");
|
||||
put("error", "none");
|
||||
put("job", "spring-boot-app");
|
||||
put("uri", "/**");
|
||||
put("outcome", "SUCCESS");
|
||||
put("status", "200");
|
||||
}
|
||||
};
|
||||
|
||||
maxValue1.put("uri", "/actuator/health");
|
||||
when(mockExecutor.execute("http_server_requests_seconds_sum")).thenReturn(List.of(sumValue1));
|
||||
promql = "(http_server_requests_seconds_count > 10 unless http_server_requests_seconds_max > 0) unless http_server_requests_seconds_sum > 10";
|
||||
result = evaluate(promql);
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(1307, result.get(0).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOrOpPromql() {
|
||||
String promql = "http_server_requests_seconds_count > 10 or jvm_threads_states_threads > 0";
|
||||
|
||||
Map<String, Object> countValue1 = new HashMap<>() {
|
||||
{
|
||||
put("exception", "none");
|
||||
put("instance", "host.docker.internal:8989");
|
||||
put("__value__", 1307);
|
||||
put("method", "GET");
|
||||
put("__name__", "http_server_requests_seconds_count");
|
||||
put("__timestamp__", "1.750320922467E9");
|
||||
put("error", "none");
|
||||
put("job", "spring-boot-app");
|
||||
put("uri", "/actuator/prometheus");
|
||||
put("outcome", "SUCCESS");
|
||||
put("status", "200");
|
||||
}
|
||||
};
|
||||
|
||||
Map<String, Object> countValue2 = new HashMap<>() {
|
||||
{
|
||||
put("exception", "none");
|
||||
put("instance", "host.docker.internal:8989");
|
||||
put("__value__", 16);
|
||||
put("method", "GET");
|
||||
put("__name__", "http_server_requests_seconds_count");
|
||||
put("__timestamp__", "1.750320922467E9");
|
||||
put("error", "none");
|
||||
put("job", "spring-boot-app");
|
||||
put("uri", "/**");
|
||||
put("outcome", "SUCCESS");
|
||||
put("status", "200");
|
||||
}
|
||||
};
|
||||
|
||||
Map<String, Object> countValue3 = new HashMap<>() {
|
||||
{
|
||||
put("exception", "none");
|
||||
put("instance", "host.docker.internal:8989");
|
||||
put("__value__", 7);
|
||||
put("method", "GET");
|
||||
put("__name__", "http_server_requests_seconds_count");
|
||||
put("__timestamp__", "1.750320922467E9");
|
||||
put("error", "none");
|
||||
put("job", "spring-boot-app");
|
||||
put("uri", "/actuator/health");
|
||||
put("outcome", "SUCCESS");
|
||||
put("status", "200");
|
||||
}
|
||||
};
|
||||
|
||||
Map<String, Object> threadsValue1 = new HashMap<>() {
|
||||
{
|
||||
put("instance", "host.docker.internal:8989");
|
||||
put("__value__", 10.007799125);
|
||||
put("__name__", "jvm_threads_states_threads");
|
||||
put("__timestamp__", "1.750320922467E9");
|
||||
put("job", "spring-boot-app");
|
||||
put("state=", "runnable");
|
||||
}
|
||||
};
|
||||
|
||||
Map<String, Object> threadsValue2 = new HashMap<>() {
|
||||
{
|
||||
put("instance", "host.docker.internal:8989");
|
||||
put("__value__", 1);
|
||||
put("__name__", "jvm_threads_states_threads");
|
||||
put("__timestamp__", "1.750320922467E9");
|
||||
put("job", "spring-boot-app");
|
||||
put("state=", "timed-waiting");
|
||||
}
|
||||
};
|
||||
|
||||
Map<String, Object> threadsValue3 = new HashMap<>() {
|
||||
{
|
||||
put("instance", "host.docker.internal:8989");
|
||||
put("__value__", 19.02);
|
||||
put("__name__", "jvm_threads_states_threads");
|
||||
put("__timestamp__", "1.750320922467E9");
|
||||
put("job", "spring-boot-app");
|
||||
put("state=", "waiting");
|
||||
}
|
||||
};
|
||||
|
||||
when(mockExecutor.execute("http_server_requests_seconds_count")).thenReturn(List.of(countValue1, countValue2, countValue3));
|
||||
when(mockExecutor.execute("jvm_threads_states_threads")).thenReturn(List.of(threadsValue1, threadsValue2, threadsValue3));
|
||||
List<Map<String, Object>> result = evaluate(promql);
|
||||
assertEquals(5, result.size());
|
||||
assertTrue(result.stream().allMatch(t -> null != t.get("__value__")));
|
||||
|
||||
|
||||
when(mockExecutor.execute("jvm_threads_states_threads")).thenReturn(new ArrayList<>());
|
||||
result = evaluate(promql);
|
||||
assertEquals(2, result.size());
|
||||
assertTrue(result.stream().allMatch(t -> null != t.get("__value__")));
|
||||
assertEquals(1307, result.get(0).get("__value__"));
|
||||
|
||||
when(mockExecutor.execute("http_server_requests_seconds_count")).thenReturn(new ArrayList<>());
|
||||
when(mockExecutor.execute("jvm_threads_states_threads")).thenReturn(List.of(threadsValue1, threadsValue2, threadsValue3));
|
||||
result = evaluate(promql);
|
||||
assertEquals(3, result.size());
|
||||
assertTrue(result.stream().allMatch(t -> null != t.get("__value__")));
|
||||
assertEquals(10.007799125, result.get(0).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMultipleUnlessConditions() {
|
||||
when(mockExecutor.execute("metric1")).thenReturn(List.of(new HashMap<>(Map.of("job", "api", "__value__", 40.0))));
|
||||
when(mockExecutor.execute("metric2")).thenReturn(List.of(new HashMap<>(Map.of("job", "web", "__value__", 50.0))));
|
||||
when(mockExecutor.execute("metric3")).thenReturn(List.of(new HashMap<>(Map.of("job", "api", "__value__", 60.0))));
|
||||
when(mockExecutor.execute("select cpu_usage from metrics where service = 'web'")).thenReturn(
|
||||
List.of(new HashMap<>(Map.of("__value__", 40.0))));
|
||||
when(mockExecutor.execute("select memory_usage from metrics where service = 'db'")).thenReturn(
|
||||
List.of(new HashMap<>(Map.of("__value__", 50.0))));
|
||||
when(mockExecutor.execute("select disk_usage from metrics where service = 'cache'")).thenReturn(
|
||||
List.of(new HashMap<>(Map.of("__value__", 60.0))));
|
||||
// promql
|
||||
List<Map<String, Object>> result = evaluate("(metric1 > 30 unless metric2 > 45) unless metric3 > 50");
|
||||
assertEquals(0, result.size());
|
||||
// sql
|
||||
result = evaluate("((select cpu_usage from metrics where service = 'web') > 30"
|
||||
+ " unless (select memory_usage from metrics where service = 'db') > 55)"
|
||||
+ " unless (select disk_usage from metrics where service = 'cache') > 50");
|
||||
assertEquals(0, result.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -167,15 +444,324 @@ class AlertExpressionEvalVisitorTest {
|
||||
when(mockExecutor.execute("cpu_temp")).thenReturn(List.of(new HashMap<>(Map.of("__value__", 75.0))));
|
||||
when(mockExecutor.execute("gpu_temp")).thenReturn(List.of(new HashMap<>(Map.of("__value__", 85.0))));
|
||||
when(mockExecutor.execute("fan_speed")).thenReturn(List.of(new HashMap<>(Map.of("__value__", 2000.0))));
|
||||
when(mockExecutor.execute("select cpu_temperature from hardware_metrics where component = 'cpu'")).thenReturn(
|
||||
List.of(new HashMap<>(Map.of("__value__", 75.0))));
|
||||
when(mockExecutor.execute("select gpu_temperature from hardware_metrics where component = 'gpu'")).thenReturn(
|
||||
List.of(new HashMap<>(Map.of("__value__", 85.0))));
|
||||
when(mockExecutor.execute("select fan_rpm from hardware_metrics where component = 'fan'")).thenReturn(
|
||||
List.of(new HashMap<>(Map.of("__value__", 2000.0))));
|
||||
|
||||
// promql
|
||||
List<Map<String, Object>> result = evaluate("(cpu_temp > 70 and gpu_temp < 90) or fan_speed > 1500");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(75.0, result.get(0).get("__value__"));
|
||||
// sql
|
||||
result = evaluate("((select cpu_temperature from hardware_metrics where component = 'cpu') > 70"
|
||||
+ " and (select gpu_temperature from hardware_metrics where component = 'gpu') < 90)"
|
||||
+ " or (select fan_rpm from hardware_metrics where component = 'fan') > 1500");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(75.0, result.get(0).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSqlAggregateCount() {
|
||||
when(mockExecutor.execute("select count(*) from cpu_metrics where host = 'server1'")).thenReturn(
|
||||
List.of(new HashMap<>(Map.of("__value__", 150))));
|
||||
List<Map<String, Object>> result = evaluate("(select count(*) from cpu_metrics where host = 'server1') > 100");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(150, result.get(0).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSqlAggregateAvg() {
|
||||
when(mockExecutor.execute("select avg(cpu_usage) from system_metrics where region = 'us-east'")).thenReturn(
|
||||
List.of(new HashMap<>(Map.of("__value__", 75.5))));
|
||||
List<Map<String, Object>> result = evaluate("(select avg(cpu_usage) from system_metrics where region = 'us-east') > 70");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(75.5, result.get(0).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSqlAggregateSum() {
|
||||
when(mockExecutor.execute("select sum(memory_used) from memory_stats where service = 'web'")).thenReturn(
|
||||
List.of(new HashMap<>(Map.of("__value__", 2048.0))));
|
||||
List<Map<String, Object>> result = evaluate("(select sum(memory_used) from memory_stats where service = 'web') < 3000");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(2048.0, result.get(0).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSqlAggregateMaxMin() {
|
||||
when(mockExecutor.execute("select max(response_time) from api_metrics where endpoint = '/api/users'")).thenReturn(
|
||||
List.of(new HashMap<>(Map.of("__value__", 250.0))));
|
||||
when(mockExecutor.execute("select min(response_time) from api_metrics where endpoint = '/api/users'")).thenReturn(
|
||||
List.of(new HashMap<>(Map.of("__value__", 50.0))));
|
||||
|
||||
List<Map<String, Object>> result = evaluate("(select max(response_time) from api_metrics where endpoint = '/api/users') > 200");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(250.0, result.get(0).get("__value__"));
|
||||
|
||||
result = evaluate("(select min(response_time) from api_metrics where endpoint = '/api/users') < 100");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(50.0, result.get(0).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSqlGroupBy() {
|
||||
when(mockExecutor.execute("select avg(cpu_usage) from system_metrics where timestamp > '2024-01-01' group by host")).thenReturn(
|
||||
List.of(new HashMap<>(Map.of("__value__", 80.5))));
|
||||
List<Map<String, Object>> result = evaluate("(select avg(cpu_usage) from system_metrics where timestamp > '2024-01-01' group by host) > 75");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(80.5, result.get(0).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSqlGroupByHaving() {
|
||||
when(mockExecutor.execute("select count(*) from error_logs where level = 'ERROR' group by service having count(*) > 10")).thenReturn(
|
||||
List.of(new HashMap<>(Map.of("__value__", 25))));
|
||||
List<Map<String, Object>> result = evaluate("(select count(*) from error_logs where level = 'ERROR' group by service having count(*) > 10) > 20");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(25, result.get(0).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSqlOrderByLimit() {
|
||||
when(mockExecutor.execute("select cpu_usage from system_metrics where host like 'web%' order by cpu_usage desc limit 1")).thenReturn(
|
||||
List.of(new HashMap<>(Map.of("__value__", 95.2))));
|
||||
List<Map<String, Object>> result = evaluate("(select cpu_usage from system_metrics where host like 'web%' order by cpu_usage desc limit 1) > 90");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(95.2, result.get(0).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSqlMultipleColumns() {
|
||||
when(mockExecutor.execute("select avg(cpu_usage), max(memory_usage) from system_metrics where region = 'us-west'")).thenReturn(
|
||||
List.of(new HashMap<>(Map.of("__value__", 65.8))));
|
||||
List<Map<String, Object>> result = evaluate("(select avg(cpu_usage), max(memory_usage) from system_metrics where region = 'us-west') < 70");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(65.8, result.get(0).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSqlSubquery() {
|
||||
when(mockExecutor.execute("select avg(cpu_usage) from system_metrics where host in (select host from active_servers where status = 'running')")).thenReturn(
|
||||
List.of(new HashMap<>(Map.of("__value__", 72.3))));
|
||||
List<Map<String, Object>> result = evaluate("(select avg(cpu_usage) from system_metrics where host in (select host from active_servers where status = 'running')) > 70");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(72.3, result.get(0).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSqlComplexSubquery() {
|
||||
when(mockExecutor.execute("select count(*) from alerts where severity = 'HIGH' and service_id in (select id from services where category = 'critical')")).thenReturn(
|
||||
List.of(new HashMap<>(Map.of("__value__", 8))));
|
||||
List<Map<String, Object>> result = evaluate("(select count(*) from alerts where severity = 'HIGH' and service_id in (select id from services where category = 'critical')) >= 5");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(8, result.get(0).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSqlWithJoins() {
|
||||
when(mockExecutor.execute("select avg(m.cpu_usage) from metrics m, servers s where m.server_id = s.id and s.environment = 'production'")).thenReturn(
|
||||
List.of(new HashMap<>(Map.of("__value__", 78.9))));
|
||||
List<Map<String, Object>> result = evaluate("(select avg(m.cpu_usage) from metrics m, servers s where m.server_id = s.id and s.environment = 'production') < 80");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(78.9, result.get(0).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSqlBetweenCondition() {
|
||||
when(mockExecutor.execute("select count(*) from performance_logs where response_time between 100 and 500")).thenReturn(
|
||||
List.of(new HashMap<>(Map.of("__value__", 1200))));
|
||||
List<Map<String, Object>> result = evaluate("(select count(*) from performance_logs where response_time between 100 and 500) > 1000");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(1200, result.get(0).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSqlComplexGroupByOrderBy() {
|
||||
when(mockExecutor.execute("select service, avg(response_time) from api_metrics where timestamp > '2024-01-01'"
|
||||
+ " group by service order by avg(response_time) desc limit 5")).thenReturn(
|
||||
List.of(new HashMap<>(Map.of("__value__", 180.5))));
|
||||
List<Map<String, Object>> result = evaluate("(select service, avg(response_time) from api_metrics where timestamp > '2024-01-01'"
|
||||
+ " group by service order by avg(response_time) desc limit 5) > 150");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(180.5, result.get(0).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSqlVarianceStddev() {
|
||||
when(mockExecutor.execute("select stddev(cpu_usage) from system_metrics where host = 'db-server' and timestamp > '2024-01-01'")).thenReturn(
|
||||
List.of(new HashMap<>(Map.of("__value__", 12.5))));
|
||||
List<Map<String, Object>> result = evaluate("(select stddev(cpu_usage) from system_metrics where host = 'db-server' and timestamp > '2024-01-01') < 15");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(12.5, result.get(0).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSqlNestedAggregation() {
|
||||
when(mockExecutor.execute("select max(daily_avg) from (select date, avg(cpu_usage) as daily_avg from system_metrics group by date) as daily_stats")).thenReturn(
|
||||
List.of(new HashMap<>(Map.of("__value__", 88.7))));
|
||||
List<Map<String, Object>> result = evaluate("(select max(daily_avg) from (select date, avg(cpu_usage) as daily_avg from system_metrics group by date) as daily_stats) > 85");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(88.7, result.get(0).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSqlComplexWhereConditions() {
|
||||
when(mockExecutor.execute("select count(*) from alerts where (severity = 'HIGH' or severity = 'CRITICAL') and status = 'ACTIVE' and created_at > '2024-01-01'")).thenReturn(
|
||||
List.of(new HashMap<>(Map.of("__value__", 42))));
|
||||
List<Map<String, Object>> result = evaluate("(select count(*) from alerts where (severity = 'HIGH' or severity = 'CRITICAL') and status = 'ACTIVE' and created_at > '2024-01-01') != 50");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(42, result.get(0).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSqlWithNullChecks() {
|
||||
when(mockExecutor.execute("select count(*) from system_metrics where cpu_usage is not null and memory_usage is not null")).thenReturn(
|
||||
List.of(new HashMap<>(Map.of("__value__", 9876))));
|
||||
List<Map<String, Object>> result = evaluate("(select count(*) from system_metrics where cpu_usage is not null and memory_usage is not null) > 9000");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(9876, result.get(0).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSqlMultipleAggregatesWithAlias() {
|
||||
when(mockExecutor.execute("select max(cpu_usage) as max_cpu, min(cpu_usage) as min_cpu, avg(cpu_usage) as avg_cpu from system_metrics where region = 'asia'")).thenReturn(
|
||||
List.of(new HashMap<>(Map.of("__value__", 92.1))));
|
||||
List<Map<String, Object>> result = evaluate("(select max(cpu_usage) as max_cpu, min(cpu_usage) as min_cpu, avg(cpu_usage) as avg_cpu from system_metrics where region = 'asia') > 90");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(92.1, result.get(0).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSqlAndPromqlCallExpr() {
|
||||
when(mockExecutor.execute("sum(rate(http_requests_total{job='your-service'}[1m]))")).thenReturn(List.of(new HashMap<>(Map.of("__value__", 80))));
|
||||
when(mockExecutor.execute("select cpu_usage from system_metrics where host = 'server1'")).thenReturn(
|
||||
List.of(new HashMap<>(Map.of("__value__", 80))));
|
||||
// promql
|
||||
List<Map<String, Object>> result = evaluate("promql(\"sum(rate(http_requests_total{job='your-service'}[1m]))\") > 70");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(80, result.get(0).get("__value__"));
|
||||
// sql
|
||||
result = evaluate("sql(\"select cpu_usage from system_metrics where host = 'server1'\") > 70");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(80, result.get(0).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAndOpPromql() {
|
||||
String promql = "http_server_requests_seconds_count > 10 and http_server_requests_seconds_max > 5";
|
||||
|
||||
Map<String, Object> countValue1 = new HashMap<>() {
|
||||
{
|
||||
put("exception", "none");
|
||||
put("instance", "host.docker.internal:8989");
|
||||
put("__value__", 1307);
|
||||
put("method", "GET");
|
||||
put("__name__", "http_server_requests_seconds_count");
|
||||
put("__timestamp__", "1.750320922467E9");
|
||||
put("error", "none");
|
||||
put("job", "spring-boot-app");
|
||||
put("uri", "/actuator/prometheus");
|
||||
put("outcome", "SUCCESS");
|
||||
put("status", "200");
|
||||
}
|
||||
};
|
||||
|
||||
Map<String, Object> countValue2 = new HashMap<>() {
|
||||
{
|
||||
put("exception", "none");
|
||||
put("instance", "host.docker.internal:8989");
|
||||
put("__value__", 16);
|
||||
put("method", "GET");
|
||||
put("__name__", "http_server_requests_seconds_count");
|
||||
put("__timestamp__", "1.750320922467E9");
|
||||
put("error", "none");
|
||||
put("job", "spring-boot-app");
|
||||
put("uri", "/**");
|
||||
put("outcome", "SUCCESS");
|
||||
put("status", "200");
|
||||
}
|
||||
};
|
||||
|
||||
Map<String, Object> countValue3 = new HashMap<>() {
|
||||
{
|
||||
put("exception", "none");
|
||||
put("instance", "host.docker.internal:8989");
|
||||
put("__value__", 7);
|
||||
put("method", "GET");
|
||||
put("__name__", "http_server_requests_seconds_count");
|
||||
put("__timestamp__", "1.750320922467E9");
|
||||
put("error", "none");
|
||||
put("job", "spring-boot-app");
|
||||
put("uri", "/actuator/health");
|
||||
put("outcome", "SUCCESS");
|
||||
put("status", "200");
|
||||
}
|
||||
};
|
||||
|
||||
Map<String, Object> maxValue1 = new HashMap<>() {
|
||||
{
|
||||
put("exception", "none");
|
||||
put("instance", "host.docker.internal:8989");
|
||||
put("__value__", 10.007799125);
|
||||
put("method", "GET");
|
||||
put("__name__", "http_server_requests_seconds_max");
|
||||
put("__timestamp__", "1.750320922467E9");
|
||||
put("error", "none");
|
||||
put("job", "spring-boot-app");
|
||||
put("uri", "/actuator/prometheus");
|
||||
put("outcome", "SUCCESS");
|
||||
put("status", "200");
|
||||
}
|
||||
};
|
||||
|
||||
Map<String, Object> maxValue2 = new HashMap<>() {
|
||||
{
|
||||
put("exception", "none");
|
||||
put("instance", "host.docker.internal:8989");
|
||||
put("__value__", 10);
|
||||
put("method", "GET");
|
||||
put("__name__", "http_server_requests_seconds_count");
|
||||
put("__timestamp__", "1.750320922467E9");
|
||||
put("error", "none");
|
||||
put("job", "spring-boot-app");
|
||||
put("uri", "/**");
|
||||
put("outcome", "SUCCESS");
|
||||
put("status", "200");
|
||||
}
|
||||
};
|
||||
|
||||
Map<String, Object> maxValue3 = new HashMap<>() {
|
||||
{
|
||||
put("exception", "none");
|
||||
put("instance", "host.docker.internal:8989");
|
||||
put("__value__", 0);
|
||||
put("method", "GET");
|
||||
put("__name__", "http_server_requests_seconds_count");
|
||||
put("__timestamp__", "1.750320922467E9");
|
||||
put("error", "none");
|
||||
put("job", "spring-boot-app");
|
||||
put("uri", "/actuator/health");
|
||||
put("outcome", "SUCCESS");
|
||||
put("status", "200");
|
||||
}
|
||||
};
|
||||
|
||||
when(mockExecutor.execute("http_server_requests_seconds_count")).thenReturn(List.of(countValue1, countValue2, countValue3));
|
||||
when(mockExecutor.execute("http_server_requests_seconds_max")).thenReturn(List.of(maxValue1, maxValue2, maxValue3));
|
||||
List<Map<String, Object>> result = evaluate(promql);
|
||||
assertEquals(2, result.size());
|
||||
assertEquals(1307, result.get(0).get("__value__"));
|
||||
assertEquals(16, result.get(1).get("__value__"));
|
||||
}
|
||||
|
||||
|
||||
private List<Map<String, Object>> evaluate(String expression) {
|
||||
AlertExpressionLexer lexer = new AlertExpressionLexer(CharStreams.fromString(expression));
|
||||
AlertExpressionParser parser = new AlertExpressionParser(new CommonTokenStream(lexer));
|
||||
return visitor.visit(parser.expression());
|
||||
CommonTokenStream tokens = new CommonTokenStream(lexer);
|
||||
AlertExpressionParser parser = new AlertExpressionParser(tokens);
|
||||
return new AlertExpressionEvalVisitor(mockExecutor, tokens).visit(parser.expression());
|
||||
}
|
||||
}
|
||||
+16
-26
@@ -20,6 +20,7 @@ package org.apache.hertzbeat.alert.notice.impl;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import org.apache.hertzbeat.alert.AlerterProperties;
|
||||
@@ -48,7 +49,7 @@ import java.util.ResourceBundle;
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class ServerChanAlertNotifyHandlerImplTest {
|
||||
|
||||
|
||||
@Mock
|
||||
private RestTemplate restTemplate;
|
||||
|
||||
@@ -57,20 +58,21 @@ class ServerChanAlertNotifyHandlerImplTest {
|
||||
|
||||
@Mock
|
||||
private ResourceBundle bundle;
|
||||
|
||||
|
||||
@InjectMocks
|
||||
private ServerChanAlertNotifyHandlerImpl serverChanAlertNotifyHandler;
|
||||
|
||||
|
||||
private NoticeReceiver receiver;
|
||||
private GroupAlert groupAlert;
|
||||
private NoticeTemplate template;
|
||||
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
receiver = new NoticeReceiver();
|
||||
receiver.setId(1L);
|
||||
receiver.setName("test-receiver");
|
||||
receiver.setAccessToken("test-token");
|
||||
receiver.setServerChanToken("SCT193569TSNm6xIabdjqeZPtOGOWcvU1e");
|
||||
|
||||
groupAlert = new GroupAlert();
|
||||
SingleAlert singleAlert = new SingleAlert();
|
||||
@@ -87,43 +89,31 @@ class ServerChanAlertNotifyHandlerImplTest {
|
||||
template.setName("test-template");
|
||||
template.setContent("test content");
|
||||
|
||||
when(alerterProperties.getServerChanWebhookUrl()).thenReturn("http://test.url/");
|
||||
when(bundle.getString("alerter.notify.title")).thenReturn("Alert Notification");
|
||||
lenient().when(alerterProperties.getServerChanWebhookUrl())
|
||||
.thenReturn("https://api.serverchan.com/send/%s");
|
||||
lenient().when(bundle.getString("alerter.notify.title")).thenReturn("Alert Notification");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testNotifyAlertSuccess() {
|
||||
CommonRobotNotifyResp successResp = new CommonRobotNotifyResp();
|
||||
successResp.setErrCode(0);
|
||||
successResp.setMsg("success");
|
||||
ResponseEntity<CommonRobotNotifyResp> responseEntity =
|
||||
new ResponseEntity<>(successResp, HttpStatus.OK);
|
||||
|
||||
ResponseEntity<CommonRobotNotifyResp> responseEntity = new ResponseEntity<>(successResp, HttpStatus.OK);
|
||||
|
||||
when(restTemplate.postForEntity(
|
||||
any(String.class),
|
||||
any(),
|
||||
eq(CommonRobotNotifyResp.class)
|
||||
)).thenReturn(responseEntity);
|
||||
eq(CommonRobotNotifyResp.class))).thenReturn(responseEntity);
|
||||
|
||||
serverChanAlertNotifyHandler.send(receiver, template, groupAlert);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testNotifyAlertFailure() {
|
||||
CommonRobotNotifyResp failResp = new CommonRobotNotifyResp();
|
||||
failResp.setCode(1);
|
||||
failResp.setErrMsg("Test Error");
|
||||
ResponseEntity<CommonRobotNotifyResp> responseEntity =
|
||||
new ResponseEntity<>(failResp, HttpStatus.BAD_REQUEST);
|
||||
|
||||
when(restTemplate.postForEntity(
|
||||
any(String.class),
|
||||
any(),
|
||||
eq(CommonRobotNotifyResp.class)
|
||||
)).thenReturn(responseEntity);
|
||||
public void testNotifyAlertWithInvalidUrl() {
|
||||
when(alerterProperties.getServerChanWebhookUrl()).thenReturn("http://invalid-url.com/%s");
|
||||
|
||||
assertThrows(AlertNoticeException.class,
|
||||
assertThrows(AlertNoticeException.class,
|
||||
() -> serverChanAlertNotifyHandler.send(receiver, template, groupAlert));
|
||||
}
|
||||
}
|
||||
|
||||
+11
-15
@@ -51,7 +51,7 @@ class SlackAlertNotifyHandlerImplTest {
|
||||
|
||||
@Mock
|
||||
private RestTemplate restTemplate;
|
||||
|
||||
|
||||
@Mock
|
||||
private ResourceBundle bundle;
|
||||
|
||||
@@ -68,18 +68,18 @@ class SlackAlertNotifyHandlerImplTest {
|
||||
receiver.setId(1L);
|
||||
receiver.setName("test-receiver");
|
||||
receiver.setAccessToken("test-token");
|
||||
receiver.setSlackWebHookUrl("http://localhost:8080");
|
||||
|
||||
receiver.setSlackWebHookUrl("https://hooks.slack.com/services/ABCDEF/GHIJKL/mnopqrstuvwxyz");
|
||||
|
||||
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");
|
||||
@@ -90,22 +90,18 @@ class SlackAlertNotifyHandlerImplTest {
|
||||
|
||||
@Test
|
||||
public void testNotifyAlertSuccess() {
|
||||
ResponseEntity<String> responseEntity =
|
||||
new ResponseEntity<>("ok", HttpStatus.OK);
|
||||
|
||||
ResponseEntity<String> responseEntity = new ResponseEntity<>("ok", HttpStatus.OK);
|
||||
|
||||
when(restTemplate.postForEntity(any(String.class), any(), eq(String.class))).thenReturn(responseEntity);
|
||||
|
||||
|
||||
slackAlertNotifyHandler.send(receiver, template, groupAlert);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotifyAlertFailure() {
|
||||
ResponseEntity<String> responseEntity =
|
||||
new ResponseEntity<>("invalid_payload", HttpStatus.BAD_REQUEST);
|
||||
public void testNotifyAlertWithInvalidUrl() {
|
||||
receiver.setSlackWebHookUrl("http://localhost:8080");
|
||||
|
||||
when(restTemplate.postForEntity(any(String.class), any(), eq(String.class))).thenReturn(responseEntity);
|
||||
|
||||
assertThrows(AlertNoticeException.class,
|
||||
assertThrows(AlertNoticeException.class,
|
||||
() -> slackAlertNotifyHandler.send(receiver, template, groupAlert));
|
||||
}
|
||||
}
|
||||
|
||||
+25
-25
@@ -20,6 +20,7 @@ package org.apache.hertzbeat.alert.notice.impl;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import org.apache.hertzbeat.alert.AlerterProperties;
|
||||
@@ -51,10 +52,10 @@ class TelegramBotAlertNotifyHandlerImplTest {
|
||||
|
||||
@Mock
|
||||
private RestTemplate restTemplate;
|
||||
|
||||
|
||||
@Mock
|
||||
private AlerterProperties alerterProperties;
|
||||
|
||||
|
||||
@Mock
|
||||
private ResourceBundle bundle;
|
||||
|
||||
@@ -70,58 +71,57 @@ class TelegramBotAlertNotifyHandlerImplTest {
|
||||
receiver = new NoticeReceiver();
|
||||
receiver.setId(1L);
|
||||
receiver.setName("test-receiver");
|
||||
receiver.setAccessToken("test-token");
|
||||
receiver.setTgBotToken("123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11");
|
||||
receiver.setTgUserId("123456789"); // Telegram specific - chat ID
|
||||
|
||||
|
||||
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");
|
||||
|
||||
when(alerterProperties.getTelegramWebhookUrl()).thenReturn("https://api.telegram.org/bot%s/sendMessage");
|
||||
when(bundle.getString("alerter.notify.title")).thenReturn("Alert Notification");
|
||||
|
||||
lenient().when(alerterProperties.getTelegramWebhookUrl())
|
||||
.thenReturn("https://api.telegram.org/bot%s/sendMessage");
|
||||
lenient().when(bundle.getString("alerter.notify.title")).thenReturn("Alert Notification");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotifyAlertSuccess() {
|
||||
TelegramBotAlertNotifyHandlerImpl.TelegramBotNotifyResponse successResp =
|
||||
new TelegramBotAlertNotifyHandlerImpl.TelegramBotNotifyResponse();
|
||||
TelegramBotAlertNotifyHandlerImpl.TelegramBotNotifyResponse successResp = new TelegramBotAlertNotifyHandlerImpl.TelegramBotNotifyResponse();
|
||||
successResp.setOk(true);
|
||||
successResp.setDescription("Test Success");
|
||||
|
||||
ResponseEntity<TelegramBotAlertNotifyHandlerImpl.TelegramBotNotifyResponse> responseEntity =
|
||||
new ResponseEntity<>(successResp, HttpStatus.OK);
|
||||
|
||||
when(restTemplate.postForEntity(any(String.class), any(),
|
||||
|
||||
ResponseEntity<TelegramBotAlertNotifyHandlerImpl.TelegramBotNotifyResponse> responseEntity = new ResponseEntity<>(
|
||||
successResp, HttpStatus.OK);
|
||||
|
||||
when(restTemplate.postForEntity(any(String.class), any(),
|
||||
eq(TelegramBotAlertNotifyHandlerImpl.TelegramBotNotifyResponse.class))).thenReturn(responseEntity);
|
||||
|
||||
|
||||
telegramBotAlertNotifyHandler.send(receiver, template, groupAlert);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotifyAlertFailure() {
|
||||
TelegramBotAlertNotifyHandlerImpl.TelegramBotNotifyResponse successResp =
|
||||
new TelegramBotAlertNotifyHandlerImpl.TelegramBotNotifyResponse();
|
||||
successResp.setOk(false);
|
||||
successResp.setDescription("Test failed");
|
||||
TelegramBotAlertNotifyHandlerImpl.TelegramBotNotifyResponse failureResp = new TelegramBotAlertNotifyHandlerImpl.TelegramBotNotifyResponse();
|
||||
failureResp.setOk(false);
|
||||
failureResp.setDescription("Test failed");
|
||||
|
||||
ResponseEntity<TelegramBotAlertNotifyHandlerImpl.TelegramBotNotifyResponse> responseEntity =
|
||||
new ResponseEntity<>(successResp, HttpStatus.BAD_REQUEST);
|
||||
ResponseEntity<TelegramBotAlertNotifyHandlerImpl.TelegramBotNotifyResponse> responseEntity = new ResponseEntity<>(
|
||||
failureResp, HttpStatus.OK);
|
||||
|
||||
when(restTemplate.postForEntity(any(String.class), any(),
|
||||
eq(TelegramBotAlertNotifyHandlerImpl.TelegramBotNotifyResponse.class))).thenReturn(responseEntity);
|
||||
|
||||
assertThrows(AlertNoticeException.class,
|
||||
|
||||
assertThrows(AlertNoticeException.class,
|
||||
() -> telegramBotAlertNotifyHandler.send(receiver, template, groupAlert));
|
||||
}
|
||||
}
|
||||
|
||||
+13
-16
@@ -48,7 +48,7 @@ import java.util.ResourceBundle;
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class WeComRobotAlertNotifyHandlerImplTest {
|
||||
|
||||
|
||||
@Mock
|
||||
private RestTemplate restTemplate;
|
||||
|
||||
@@ -57,20 +57,21 @@ class WeComRobotAlertNotifyHandlerImplTest {
|
||||
|
||||
@Mock
|
||||
private ResourceBundle bundle;
|
||||
|
||||
|
||||
@InjectMocks
|
||||
private WeComRobotAlertNotifyHandlerImpl weComRobotAlertNotifyHandler;
|
||||
|
||||
|
||||
private NoticeReceiver receiver;
|
||||
private GroupAlert groupAlert;
|
||||
private NoticeTemplate template;
|
||||
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
receiver = new NoticeReceiver();
|
||||
receiver.setId(1L);
|
||||
receiver.setName("test-receiver");
|
||||
receiver.setAccessToken("test-token");
|
||||
receiver.setWechatId("test-wechat-id");
|
||||
|
||||
groupAlert = new GroupAlert();
|
||||
SingleAlert singleAlert = new SingleAlert();
|
||||
@@ -95,33 +96,29 @@ class WeComRobotAlertNotifyHandlerImplTest {
|
||||
public void testNotifyAlertSuccess() {
|
||||
CommonRobotNotifyResp successResp = new CommonRobotNotifyResp();
|
||||
successResp.setErrCode(0);
|
||||
ResponseEntity<CommonRobotNotifyResp> responseEntity =
|
||||
new ResponseEntity<>(successResp, HttpStatus.OK);
|
||||
|
||||
ResponseEntity<CommonRobotNotifyResp> responseEntity = new ResponseEntity<>(successResp, HttpStatus.OK);
|
||||
|
||||
when(restTemplate.postForEntity(
|
||||
any(String.class),
|
||||
any(),
|
||||
eq(CommonRobotNotifyResp.class)
|
||||
)).thenReturn(responseEntity);
|
||||
eq(CommonRobotNotifyResp.class))).thenReturn(responseEntity);
|
||||
|
||||
weComRobotAlertNotifyHandler.send(receiver, template, groupAlert);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testNotifyAlertFailure() {
|
||||
CommonRobotNotifyResp failResp = new CommonRobotNotifyResp();
|
||||
failResp.setCode(1);
|
||||
failResp.setErrMsg("Test Error");
|
||||
ResponseEntity<CommonRobotNotifyResp> responseEntity =
|
||||
new ResponseEntity<>(failResp, HttpStatus.OK);
|
||||
|
||||
ResponseEntity<CommonRobotNotifyResp> responseEntity = new ResponseEntity<>(failResp, HttpStatus.OK);
|
||||
|
||||
when(restTemplate.postForEntity(
|
||||
any(String.class),
|
||||
any(),
|
||||
eq(CommonRobotNotifyResp.class)
|
||||
)).thenReturn(responseEntity);
|
||||
eq(CommonRobotNotifyResp.class))).thenReturn(responseEntity);
|
||||
|
||||
assertThrows(AlertNoticeException.class,
|
||||
assertThrows(AlertNoticeException.class,
|
||||
() -> weComRobotAlertNotifyHandler.send(receiver, template, groupAlert));
|
||||
}
|
||||
}
|
||||
|
||||
+57
-13
@@ -17,19 +17,7 @@
|
||||
|
||||
package org.apache.hertzbeat.alert.service;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.anySet;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.apache.hertzbeat.alert.calculate.PeriodicAlertRuleScheduler;
|
||||
import org.apache.hertzbeat.alert.dao.AlertDefineDao;
|
||||
import org.apache.hertzbeat.alert.service.impl.AlertDefineServiceImpl;
|
||||
@@ -45,6 +33,27 @@ import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.apache.hertzbeat.common.constants.CommonConstants.ALERT_THRESHOLD_TYPE_PERIODIC;
|
||||
import static org.apache.hertzbeat.common.constants.CommonConstants.ALERT_THRESHOLD_TYPE_REALTIME;
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
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.eq;
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.anySet;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Test case for {@link AlertDefineService}
|
||||
*/
|
||||
@@ -62,6 +71,9 @@ class AlertDefineServiceTest {
|
||||
@Mock
|
||||
private List<AlertDefineImExportService> alertDefineImExportServiceList;
|
||||
|
||||
@Mock
|
||||
private DataSourceService dataSourceService;
|
||||
|
||||
@InjectMocks
|
||||
private AlertDefineServiceImpl alertDefineService;
|
||||
|
||||
@@ -131,4 +143,36 @@ class AlertDefineServiceTest {
|
||||
assertNotNull(alertDefineService.getAlertDefines(null, null, "id", "desc", 1, 10));
|
||||
verify(alertDefineDao, times(1)).findAll(any(Specification.class), any(PageRequest.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getDefinePreview() {
|
||||
String expr = "http_server_requests_seconds_count > 10";
|
||||
|
||||
Map<String, Object> countValue1 = new HashMap<>() {
|
||||
{
|
||||
put("exception", "none");
|
||||
put("instance", "host.docker.internal:8989");
|
||||
put("__value__", 1307);
|
||||
put("method", "GET");
|
||||
put("__name__", "http_server_requests_seconds_count");
|
||||
put("__timestamp__", "1.750320922467E9");
|
||||
put("error", "none");
|
||||
put("job", "spring-boot-app");
|
||||
put("uri", "/actuator/prometheus");
|
||||
put("outcome", "SUCCESS");
|
||||
put("status", "200");
|
||||
}
|
||||
};
|
||||
when(dataSourceService.calculate(eq("promql"), eq(expr))).thenReturn(Lists.newArrayList(countValue1));
|
||||
List<Map<String, Object>> result = alertDefineService.getDefinePreview("promql", ALERT_THRESHOLD_TYPE_PERIODIC, expr);
|
||||
assertNotNull(result);
|
||||
assertEquals(1307, result.get(0).get("__value__"));
|
||||
|
||||
result = alertDefineService.getDefinePreview("promql", ALERT_THRESHOLD_TYPE_PERIODIC, null);
|
||||
assertEquals(0, result.size());
|
||||
|
||||
result = alertDefineService.getDefinePreview("promql", ALERT_THRESHOLD_TYPE_REALTIME, null);
|
||||
assertEquals(0, result.size());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* 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.service;
|
||||
|
||||
|
||||
import org.apache.hertzbeat.alert.dto.AlibabaCloudSlsExternAlert;
|
||||
import org.apache.hertzbeat.alert.reduce.AlarmCommonReduce;
|
||||
import org.apache.hertzbeat.alert.service.impl.AlibabaCloudSlsExternAlertService;
|
||||
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
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 java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* unit test for {@link AlibabaCloudSlsExternAlertServiceTest }
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class AlibabaCloudSlsExternAlertServiceTest {
|
||||
|
||||
@Mock
|
||||
private AlarmCommonReduce alarmCommonReduce;
|
||||
|
||||
@InjectMocks
|
||||
private AlibabaCloudSlsExternAlertService externAlertService;
|
||||
|
||||
@Test
|
||||
void testAddExternAlertWithInvalidContent() {
|
||||
String invalidContent = "invalid json";
|
||||
externAlertService.addExternAlert(invalidContent);
|
||||
verify(alarmCommonReduce, never()).reduceAndSendAlarm(any(SingleAlert.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddExternAlert() {
|
||||
String source = externAlertService.supportSource();
|
||||
assertEquals("alibabacloud-sls", source);
|
||||
|
||||
AlibabaCloudSlsExternAlert externAlert = new AlibabaCloudSlsExternAlert();
|
||||
externAlert.setAlertName("Test SLS alert");
|
||||
externAlert.setFireTime((int) Instant.now().getEpochSecond());
|
||||
externAlert.setAlertTime((int) Instant.now().getEpochSecond());
|
||||
externAlert.setRegion("cn-hangzhou");
|
||||
externAlert.setProject("project");
|
||||
externAlert.setStatus("firing");
|
||||
externAlert.setSeverity(AlibabaCloudSlsExternAlert.Severity.HIGH.getStatus());
|
||||
|
||||
Map<String, String> labels = new HashMap<>();
|
||||
labels.put("labels-k", "labels-v");
|
||||
externAlert.setLabels(labels);
|
||||
|
||||
Map<String, String> annotations = new HashMap<>();
|
||||
annotations.put("annotations-k", "annotations-v");
|
||||
externAlert.setAnnotations(annotations);
|
||||
|
||||
externAlertService.addExternAlert(JsonUtil.toJson(externAlert));
|
||||
|
||||
verify(alarmCommonReduce, times(1)).reduceAndSendAlarm(any(SingleAlert.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddExternAlertWithSigninUrl() {
|
||||
AlibabaCloudSlsExternAlert alert = new AlibabaCloudSlsExternAlert();
|
||||
alert.setAlertName("Test Alert");
|
||||
alert.setFireTime((int) Instant.now().getEpochSecond());
|
||||
alert.setAlertTime((int) Instant.now().getEpochSecond());
|
||||
alert.setRegion("cn-hangzhou");
|
||||
alert.setProject("test-project");
|
||||
alert.setStatus("firing");
|
||||
alert.setSeverity(AlibabaCloudSlsExternAlert.Severity.HIGH.getStatus());
|
||||
alert.setSigninUrl("https://example.com");
|
||||
externAlertService.addExternAlert(JsonUtil.toJson(alert));
|
||||
verify(alarmCommonReduce, times(1)).reduceAndSendAlarm(any(SingleAlert.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddExternAlertWithDifferentSeverityLevels() {
|
||||
for (AlibabaCloudSlsExternAlert.Severity severity : AlibabaCloudSlsExternAlert.Severity.values()) {
|
||||
AlibabaCloudSlsExternAlert alert = new AlibabaCloudSlsExternAlert();
|
||||
alert.setAlertName("Test Alert");
|
||||
alert.setFireTime((int) Instant.now().getEpochSecond());
|
||||
alert.setAlertTime((int) Instant.now().getEpochSecond());
|
||||
alert.setRegion("cn-hangzhou");
|
||||
alert.setProject("test-project");
|
||||
alert.setStatus("firing");
|
||||
alert.setSeverity(severity.getStatus());
|
||||
externAlertService.addExternAlert(JsonUtil.toJson(alert));
|
||||
}
|
||||
verify(alarmCommonReduce, times(AlibabaCloudSlsExternAlert.Severity.values().length))
|
||||
.reduceAndSendAlarm(any(SingleAlert.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddExternAlertWithEmptyLabelsAndAnnotations() {
|
||||
AlibabaCloudSlsExternAlert alert = new AlibabaCloudSlsExternAlert();
|
||||
alert.setAlertName("Test Alert");
|
||||
alert.setFireTime((int) Instant.now().getEpochSecond());
|
||||
alert.setAlertTime((int) Instant.now().getEpochSecond());
|
||||
alert.setRegion("cn-hangzhou");
|
||||
alert.setProject("test-project");
|
||||
alert.setStatus("firing");
|
||||
alert.setSeverity(AlibabaCloudSlsExternAlert.Severity.HIGH.getStatus());
|
||||
alert.setLabels(new HashMap<>());
|
||||
alert.setAnnotations(new HashMap<>());
|
||||
externAlertService.addExternAlert(JsonUtil.toJson(alert));
|
||||
verify(alarmCommonReduce, times(1)).reduceAndSendAlarm(any(SingleAlert.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddExternAlertWithInvalidSeverity() {
|
||||
AlibabaCloudSlsExternAlert alert = new AlibabaCloudSlsExternAlert();
|
||||
alert.setAlertName("Test Alert");
|
||||
alert.setFireTime((int) Instant.now().getEpochSecond());
|
||||
alert.setAlertTime((int) Instant.now().getEpochSecond());
|
||||
alert.setRegion("cn-hangzhou");
|
||||
alert.setProject("test-project");
|
||||
alert.setStatus("firing");
|
||||
alert.setSeverity(-99);
|
||||
externAlertService.addExternAlert(JsonUtil.toJson(alert));
|
||||
verify(alarmCommonReduce, times(1)).reduceAndSendAlarm(any(SingleAlert.class));
|
||||
}
|
||||
|
||||
}
|
||||
+64
-41
@@ -17,29 +17,35 @@
|
||||
|
||||
package org.apache.hertzbeat.alert.service;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import java.util.HashMap;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import org.antlr.v4.runtime.CommonTokenStream;
|
||||
import org.antlr.v4.runtime.tree.ParseTree;
|
||||
import org.apache.hertzbeat.alert.service.impl.DataSourceServiceImpl;
|
||||
import org.apache.hertzbeat.common.support.exception.AlertExpressionException;
|
||||
import org.apache.hertzbeat.warehouse.db.QueryExecutor;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* test case for {@link DataSourceService}
|
||||
*/
|
||||
class DataSourceServiceTest {
|
||||
|
||||
|
||||
private DataSourceServiceImpl dataSourceService;
|
||||
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
dataSourceService = new DataSourceServiceImpl();
|
||||
@@ -51,12 +57,12 @@ class DataSourceServiceTest {
|
||||
new HashMap<>(Map.of("__value__", 100.0, "timestamp", 1343554, "instance", "node1")),
|
||||
new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555, "instance", "node2"))
|
||||
);
|
||||
|
||||
|
||||
QueryExecutor mockExecutor = Mockito.mock(QueryExecutor.class);
|
||||
Mockito.when(mockExecutor.support("promql")).thenReturn(true);
|
||||
Mockito.when(mockExecutor.execute(Mockito.anyString())).thenReturn(prometheusData);
|
||||
dataSourceService.setExecutors(List.of(mockExecutor));
|
||||
|
||||
|
||||
List<Map<String, Object>> result = dataSourceService.calculate("promql", "node_cpu_seconds_total > 150");
|
||||
assertEquals(2, result.size());
|
||||
assertNull(result.get(0).get("__value__"));
|
||||
@@ -296,45 +302,36 @@ class DataSourceServiceTest {
|
||||
@Test
|
||||
void calculate15() {
|
||||
List<Map<String, Object>> prometheusData1 = List.of(
|
||||
new HashMap<>(Map.of("__value__", 100.0, "timestamp", 1343554, "instance", "node1")),
|
||||
new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555, "instance", "node2"))
|
||||
new HashMap<>(Map.of("__value__", 1))
|
||||
);
|
||||
List<Map<String, Object>> prometheusData2 = List.of(
|
||||
new HashMap<>(Map.of("__value__", 100.0, "timestamp", 1343554, "instance", "node1")),
|
||||
new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555, "instance", "node2"))
|
||||
);
|
||||
|
||||
QueryExecutor mockExecutor = Mockito.mock(QueryExecutor.class);
|
||||
Mockito.when(mockExecutor.support("promql")).thenReturn(true);
|
||||
Mockito.when(mockExecutor.execute("node_cpu_seconds_total{mode=\"user\"}")).thenReturn(prometheusData1);
|
||||
Mockito.when(mockExecutor.execute("node_cpu_seconds_total{mode=\"idle\"}")).thenReturn(prometheusData2);
|
||||
Mockito.when(mockExecutor.execute("count(node_cpu_seconds_total{mode=\"user\"} > 250)")).thenReturn(prometheusData1);
|
||||
Mockito.when(mockExecutor.execute("count(node_cpu_seconds_total{mode=\"idle\"} < 220 )")).thenReturn(new ArrayList<>());
|
||||
dataSourceService.setExecutors(List.of(mockExecutor));
|
||||
|
||||
List<Map<String, Object>> result = dataSourceService.calculate("promql", "node_cpu_seconds_total{mode=\"user\"} > 250 and node_cpu_seconds_total{mode=\"idle\"} < 220");
|
||||
assertEquals(1, result.size());
|
||||
assertNull(result.get(0).get("__value__"));
|
||||
List<Map<String, Object>> result = dataSourceService.calculate("promql", "count(node_cpu_seconds_total{mode=\"user\"} > 250) > 0 and count(node_cpu_seconds_total{mode=\"idle\"} < 220 ) > 0");
|
||||
assertEquals(0, result.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void calculate16() {
|
||||
List<Map<String, Object>> prometheusData1 = List.of(
|
||||
new HashMap<>(Map.of("__value__", 100.0, "timestamp", 1343554, "instance", "node1")),
|
||||
new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555, "instance", "node2"))
|
||||
new HashMap<>(Map.of("__value__", 1))
|
||||
);
|
||||
List<Map<String, Object>> prometheusData2 = List.of(
|
||||
new HashMap<>(Map.of("__value__", 100.0, "timestamp", 1343554, "instance", "node1")),
|
||||
new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555, "instance", "node2"))
|
||||
new HashMap<>(Map.of("__value__", 1))
|
||||
);
|
||||
|
||||
QueryExecutor mockExecutor = Mockito.mock(QueryExecutor.class);
|
||||
Mockito.when(mockExecutor.support("promql")).thenReturn(true);
|
||||
Mockito.when(mockExecutor.execute("node_cpu_seconds_total{mode=\"user\"}")).thenReturn(prometheusData1);
|
||||
Mockito.when(mockExecutor.execute("node_cpu_seconds_total{mode=\"idle\"}")).thenReturn(prometheusData2);
|
||||
Mockito.when(mockExecutor.execute("count(node_cpu_seconds_total{mode=\"user\"} > 250)")).thenReturn(prometheusData1);
|
||||
Mockito.when(mockExecutor.execute("count(node_cpu_seconds_total{mode=\"idle\"} < 220 )")).thenReturn(prometheusData2);
|
||||
dataSourceService.setExecutors(List.of(mockExecutor));
|
||||
|
||||
List<Map<String, Object>> result = dataSourceService.calculate("promql", "node_cpu_seconds_total{mode=\"user\"} > 50 and node_cpu_seconds_total{mode=\"idle\"} < 20");
|
||||
List<Map<String, Object>> result = dataSourceService.calculate("promql", "count(node_cpu_seconds_total{mode=\"user\"} > 250) > 0 and count(node_cpu_seconds_total{mode=\"idle\"} < 220 ) > 0");
|
||||
assertEquals(1, result.size());
|
||||
assertNull(result.get(0).get("__value__"));
|
||||
assertNotNull(result.get(0).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -399,8 +396,7 @@ class DataSourceServiceTest {
|
||||
dataSourceService.setExecutors(List.of(mockExecutor));
|
||||
|
||||
List<Map<String, Object>> result = dataSourceService.calculate("promql", "node_cpu_seconds_total{mode=\"user\"} > 250 or node_cpu_seconds_total{mode=\"idle\"} < 20");
|
||||
assertEquals(1, result.size());
|
||||
assertNull(result.get(0).get("__value__"));
|
||||
assertEquals(0, result.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -421,8 +417,7 @@ class DataSourceServiceTest {
|
||||
dataSourceService.setExecutors(List.of(mockExecutor));
|
||||
|
||||
List<Map<String, Object>> result = dataSourceService.calculate("promql", "node_cpu_seconds_total{mode=\"user\"} > 50 or node_cpu_seconds_total{mode=\"idle\"} < 320");
|
||||
assertEquals(1, result.size());
|
||||
assertNotNull(result.get(0).get("__value__"));
|
||||
assertEquals(4, result.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -443,8 +438,8 @@ class DataSourceServiceTest {
|
||||
dataSourceService.setExecutors(List.of(mockExecutor));
|
||||
|
||||
List<Map<String, Object>> result = dataSourceService.calculate("promql", "node_cpu_seconds_total{mode=\"user\"} > 50 unless node_cpu_seconds_total{mode=\"idle\"} < 320");
|
||||
assertEquals(1, result.size());
|
||||
assertNull(result.get(0).get("__value__"));
|
||||
assertEquals(2, result.size());
|
||||
assertEquals(100.0, result.get(0).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -465,8 +460,8 @@ class DataSourceServiceTest {
|
||||
dataSourceService.setExecutors(List.of(mockExecutor));
|
||||
|
||||
List<Map<String, Object>> result = dataSourceService.calculate("promql", "node_cpu_seconds_total{mode=\"user\"} > 50 unless node_cpu_seconds_total{mode=\"idle\"} < 20");
|
||||
assertEquals(1, result.size());
|
||||
assertNotNull(result.get(0).get("__value__"));
|
||||
assertEquals(2, result.size());
|
||||
assertEquals(100.0, result.get(0).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -487,8 +482,7 @@ class DataSourceServiceTest {
|
||||
dataSourceService.setExecutors(List.of(mockExecutor));
|
||||
|
||||
List<Map<String, Object>> result = dataSourceService.calculate("promql", "node_cpu_seconds_total{mode=\"user\"} > 250 unless node_cpu_seconds_total{mode=\"idle\"} < 20");
|
||||
assertEquals(1, result.size());
|
||||
assertNull(result.get(0).get("__value__"));
|
||||
assertEquals(0, result.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -565,7 +559,6 @@ class DataSourceServiceTest {
|
||||
tokenStreamCache.invalidateAll();
|
||||
long beforeHits = tokenStreamCache.stats().hitCount();
|
||||
dataSourceService.calculate("promql", expr);
|
||||
expressionCache.invalidateAll();
|
||||
dataSourceService.calculate("promql", expr);
|
||||
long actualHits = tokenStreamCache.stats().hitCount() - beforeHits;
|
||||
assertEquals(1, actualHits, "expression cache should hit but miss");
|
||||
@@ -616,4 +609,34 @@ class DataSourceServiceTest {
|
||||
long actualHits = tokenStreamCache.stats().hitCount() - beforeHits;
|
||||
assertEquals(0, actualHits, "expression cache should miss but hit");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAlertExpressionException() {
|
||||
List<Map<String, Object>> prometheusData = List.of(
|
||||
new HashMap<>() {
|
||||
{
|
||||
put("exception", "none");
|
||||
put("instance", "host.docker.internal:8989");
|
||||
put("__value__", 1307);
|
||||
put("method", "GET");
|
||||
put("__name__", "http_server_requests_seconds_count");
|
||||
put("__timestamp__", "1.750320922467E9");
|
||||
put("error", "none");
|
||||
put("job", "spring-boot-app");
|
||||
put("uri", "/actuator/prometheus");
|
||||
put("outcome", "SUCCESS");
|
||||
put("status", "200");
|
||||
}
|
||||
});
|
||||
QueryExecutor mockExecutor = Mockito.mock(QueryExecutor.class);
|
||||
when(mockExecutor.support(eq("promql"))).thenReturn(true);
|
||||
when(mockExecutor.execute(eq("http_server_requests_seconds_count"))).thenReturn(prometheusData);
|
||||
|
||||
dataSourceService.setExecutors(List.of(mockExecutor));
|
||||
List<Map<String, Object>> result = dataSourceService.calculate("promql", "http_server_requests_seconds_count > 10");
|
||||
assertNotNull(result);
|
||||
assertEquals(1307, result.get(0).get("__value__"));
|
||||
|
||||
assertThrows(AlertExpressionException.class, () -> dataSourceService.calculate("promql", "http_server_requests_seconds_count{!@~!!#$%^&}"));
|
||||
}
|
||||
}
|
||||
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* 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.service;
|
||||
|
||||
import org.apache.hertzbeat.alert.dto.HuaweiCloudExternAlert;
|
||||
import org.apache.hertzbeat.alert.reduce.AlarmCommonReduce;
|
||||
import org.apache.hertzbeat.alert.service.impl.HuaweiCloudExternAlertService;
|
||||
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
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 static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* unit test for {@link AlibabaCloudSlsExternAlertServiceTest }
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class HuaweiCloudExternAlertServiceTest {
|
||||
|
||||
@Mock
|
||||
private AlarmCommonReduce alarmCommonReduce;
|
||||
|
||||
@InjectMocks
|
||||
private HuaweiCloudExternAlertService externAlertService;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
assertEquals("huaweicloud-ces", externAlertService.supportSource());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddExternAlertWithInvalidContent() {
|
||||
String invalidContent = "invalid json";
|
||||
externAlertService.addExternAlert(invalidContent);
|
||||
verify(alarmCommonReduce, never()).reduceAndSendAlarm(any(SingleAlert.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMessageValidFailed() {
|
||||
HuaweiCloudExternAlert externAlert = new HuaweiCloudExternAlert();
|
||||
externAlert.setMessageId("d3672d737bb742cf8c2aa3f0fd72d4d1");
|
||||
externAlert.setType("failedType");
|
||||
externAlert.setMessage("failedMessage");
|
||||
externAlert.setTimestamp("2025-06-07T15:12:09Z");
|
||||
externAlertService.addExternAlert(JsonUtil.toJson(externAlert));
|
||||
verify(alarmCommonReduce, never()).reduceAndSendAlarm(any(SingleAlert.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCertFailed() {
|
||||
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://smn.cn-north-4.myhuaweicloud.com/failedUrl");
|
||||
externAlert.setTimestamp("2025-06-07T15:12:09Z");
|
||||
externAlertService.addExternAlert(JsonUtil.toJson(externAlert));
|
||||
verify(alarmCommonReduce, never()).reduceAndSendAlarm(any(SingleAlert.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddExternAlert() {
|
||||
HuaweiCloudExternAlert externAlert = new HuaweiCloudExternAlert();
|
||||
externAlert.setSignature("Igs0bBhzw0JGmlgBH+9ejw2xWfPTXjAatAEsKDkkWcC5bZ/jveckdRZdgp/S0JER9eiJfMF427YDABufIN0sv/vBRXaRQKfRBLTJYbSTl+AQpEbIW5yUfJSRLEG3HNEhUDjASolbrW7zdPCoGkkqjifE23FCvw"
|
||||
+ "+4tewMzqmHnfJHcFBq3W89CJzdPBjwO1UcY9C39moUZgqZk+qDVLpxb4bHSrEYAwPOSrOPR7TZpETJ30UOgFYajJydQk692edfs0NeVutHoQiOJ5/YC83ULHft0aXhichjtfZE4KF69nROAKez0ubk3l"
|
||||
+ "Ey/mBIM9Ylbxn5b84OIrzzZQrIWe8Syw==");
|
||||
externAlert.setSubject("[华为云][紧急告警]云监控通知:分布式缓存服务-DCS Redis实例 “dcs-h4tv” 的每秒并发操作数已触发告警。");
|
||||
externAlert.setTopicUrn("urn:smn:cn-north-4:477a784601d744e4ab9ab83986502d31:CES_notification_group_bngJ2aMpX");
|
||||
externAlert.setMessageId("1565df032a19494590d61e05f7b0dc0e");
|
||||
externAlert.setType("Notification");
|
||||
externAlert.setMessage("{\"version\":\"v1\",\"data\":{\"AccountName\":\"hid_hk6tij5o1v-95zn\",\"Namespace\":\"分布式缓存服务\",\"DimensionName\":\"DCS Redis实例\",\"ResourceName\""
|
||||
+ ":\"dcs-h4tv\",\"MetricName\":\"每秒并发操作数\",\"IsAlarm\":true,\"AlarmLevel\":\"紧急\",\"Region\":\"华东-上海一\",\"RegionId\":\"cn-east-3\",\"ResourceId\":\"3dc7b9ea"
|
||||
+ "-70b4-4c38-942d-e2636e6d844c\",\"PrivateIp\":\"192.168.0.54\",\"CurrentData\":\"6.00 count\",\"AlarmTime\":\"2025/06/02 22:56:15 GMT+08:00\","
|
||||
+ "\"AlarmRecordID\":\"ah1748876175242njvndyzMZ\","
|
||||
+ "\"AlarmRuleName\":\"alarm-c5jj\",\"IsOriginalValue\":true,\"Filter\":\"原始值\",\"ComparisonOperator\":\"\\u003e\",\"Value\":\"5 count\",\"Unit\":\"count\",\"Count\":2}}");
|
||||
externAlert.setSigningCertUrl("https://smn.cn-north-4.myhuaweicloud.com/smn/SMN_cn-north-4_b98100ca131b4116ab8ee7ccedbaae99.pem");
|
||||
externAlert.setTimestamp("2025-06-02T14:56:17Z");
|
||||
externAlertService.addExternAlert(JsonUtil.toJson(externAlert));
|
||||
verify(alarmCommonReduce, times(1)).reduceAndSendAlarm(any(SingleAlert.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSubscriptionUrl() {
|
||||
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://smn.cn-north-4.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 testUnsubscribe() {
|
||||
HuaweiCloudExternAlert externAlert = new HuaweiCloudExternAlert();
|
||||
externAlert.setSignature("TImrLoeb0tV1JZJSPyA0rpC9mNqH3MmhwQ4tgpuHHa+JztfGVZFvkU//OthKKhzpDAoYiXOYG9DbzXCLbvaGePIRITakoynYyYr9zZIpdx9jXhQNlgF8np"
|
||||
+ "1+t0JxNeoIq0DYWgH52tsodwqOm+OnmkcHwCRo/1rFv85KrKAaX2gy3sNwXw1hKnAwAw0mJlxHHSf/N3+7j6GoxCNV7fN9K4CpJiLMGNvUa7zVmG0U9mPvt/7Lac155kPPQ9l"
|
||||
+ "YyeL7vVI0e4sfRbuQruz3E0+ZP40TKx0afoeR0/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("UnsubscribeConfirmation");
|
||||
externAlert.setMessage("{}");
|
||||
externAlert.setSigningCertUrl("https://smn.cn-north-4.myhuaweicloud.com/smn/SMN_cn-north-4_b98100ca131b4116ab8ee7ccedbaae99.pem");
|
||||
externAlert.setTimestamp("2025-06-07T15:12:09Z");
|
||||
externAlertService.addExternAlert(JsonUtil.toJson(externAlert));
|
||||
verify(alarmCommonReduce, never()).reduceAndSendAlarm(any(SingleAlert.class));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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.service;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.apache.hertzbeat.alert.config.SmsConfig;
|
||||
import org.apache.hertzbeat.alert.config.SmslocalSmsProperties;
|
||||
import org.apache.hertzbeat.base.dao.GeneralConfigDao;
|
||||
import org.apache.hertzbeat.common.constants.GeneralConfigTypeEnum;
|
||||
import org.apache.hertzbeat.common.entity.manager.GeneralConfig;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
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 static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
|
||||
/**
|
||||
* unit test for {@link SmsClientFactory }
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class SmsClientFactoryTest {
|
||||
|
||||
|
||||
@Mock
|
||||
private GeneralConfigDao generalConfigDao;
|
||||
|
||||
@Mock
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@Mock
|
||||
private SmsConfig yamlSmsConfig;
|
||||
|
||||
@InjectMocks
|
||||
private SmsClientFactory smsClientFactory;
|
||||
|
||||
|
||||
@Test
|
||||
void testloadDbConfig() throws JsonProcessingException {
|
||||
GeneralConfig generalConfig = new GeneralConfig();
|
||||
|
||||
SmsConfig smsConfig = new SmsConfig();
|
||||
smsConfig.setType("smslocal");
|
||||
smsConfig.setEnable(true);
|
||||
smsConfig.setSmslocal(new SmslocalSmsProperties("11"));
|
||||
|
||||
generalConfig.setContent(JsonUtil.toJson(smsConfig));
|
||||
when(objectMapper.readValue(generalConfig.getContent(), SmsConfig.class)).thenReturn(smsConfig);
|
||||
when(generalConfigDao.findByType(GeneralConfigTypeEnum.sms.name())).thenReturn(generalConfig);
|
||||
|
||||
assertNotNull(smsClientFactory.getSmsClient());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void testloadYamlConfig() {
|
||||
when(generalConfigDao.findByType(GeneralConfigTypeEnum.sms.name())).thenReturn(null);
|
||||
when(yamlSmsConfig.getType()).thenReturn("smslocal");
|
||||
when(yamlSmsConfig.isEnable()).thenReturn(true);
|
||||
when(yamlSmsConfig.getSmslocal()).thenReturn(new SmslocalSmsProperties("11"));
|
||||
assertNotNull(smsClientFactory.getSmsClient());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNull() {
|
||||
when(generalConfigDao.findByType(GeneralConfigTypeEnum.sms.name())).thenReturn(null);
|
||||
when(yamlSmsConfig.getType()).thenReturn("");
|
||||
assertNull(smsClientFactory.getSmsClient());
|
||||
}
|
||||
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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.service.impl;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import org.apache.hertzbeat.alert.config.SmslocalSmsProperties;
|
||||
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
|
||||
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
|
||||
import org.apache.hertzbeat.common.support.exception.SendMessageException;
|
||||
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 static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Test case for {@link SmsLocalSmsClientImpl}
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class SmsLocalSmsClientImplTest {
|
||||
|
||||
@Mock
|
||||
private SmslocalSmsProperties smslocalSmsProperties;
|
||||
|
||||
private SmsLocalSmsClientImpl smsLocalSmsClient;
|
||||
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
smsLocalSmsClient = new SmsLocalSmsClientImpl(smslocalSmsProperties);
|
||||
when(smslocalSmsProperties.getApiKey()).thenReturn("2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSendMessage() {
|
||||
assertEquals("smslocal", smsLocalSmsClient.getType());
|
||||
assertTrue(smsLocalSmsClient.checkConfig());
|
||||
//
|
||||
NoticeReceiver noticeReceiver = new NoticeReceiver();
|
||||
noticeReceiver.setPhone("13888888888");
|
||||
|
||||
SingleAlert singleAlert = new SingleAlert();
|
||||
singleAlert.setContent("test");
|
||||
|
||||
GroupAlert groupAlert = new GroupAlert();
|
||||
groupAlert.setAlerts(Lists.newArrayList(singleAlert));
|
||||
|
||||
assertThrows(SendMessageException.class,
|
||||
() -> smsLocalSmsClient.sendMessage(noticeReceiver, null, groupAlert));
|
||||
}
|
||||
|
||||
}
|
||||
+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.alert.service.impl;
|
||||
|
||||
import org.apache.hertzbeat.alert.config.UniSmsProperties;
|
||||
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
|
||||
import org.apache.hertzbeat.common.support.exception.SendMessageException;
|
||||
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.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
|
||||
/**
|
||||
* Test case for {@link UniSmsClientImpl}
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class UniSmsClientImplTest {
|
||||
|
||||
@Mock
|
||||
private UniSmsProperties uniSmsProperties;
|
||||
|
||||
private UniSmsClientImpl uniSmsClient;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
uniSmsClient = new UniSmsClientImpl(uniSmsProperties);
|
||||
when(uniSmsProperties.getSignature()).thenReturn("2");
|
||||
when(uniSmsProperties.getTemplateId()).thenReturn("any(String.class)");
|
||||
when(uniSmsProperties.getAuthMode()).thenReturn("hmac");
|
||||
when(uniSmsProperties.getAccessKeyId()).thenReturn("hmac");
|
||||
when(uniSmsProperties.getAccessKeySecret()).thenReturn("hmac");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSendMessage() {
|
||||
assertEquals("unisms", uniSmsClient.getType());
|
||||
assertTrue(uniSmsClient.checkConfig());
|
||||
//
|
||||
NoticeReceiver noticeReceiver = new NoticeReceiver();
|
||||
noticeReceiver.setPhone("13888888888");
|
||||
|
||||
Map<String, String> commonLabels = new HashMap<>();
|
||||
commonLabels.put("instance", "");
|
||||
commonLabels.put("priority", "unknown");
|
||||
|
||||
Map<String, String> commonAnnotations = new HashMap<>();
|
||||
commonAnnotations.put("test", "test");
|
||||
|
||||
GroupAlert groupAlert = new GroupAlert();
|
||||
groupAlert.setCommonLabels(commonLabels);
|
||||
groupAlert.setCommonAnnotations(commonAnnotations);
|
||||
|
||||
assertThrows(SendMessageException.class,
|
||||
() -> uniSmsClient.sendMessage(noticeReceiver, null, groupAlert));
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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.alert.util;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* Test case for {@link CryptoUtils}
|
||||
*/
|
||||
public class CryptoUtilsTest {
|
||||
|
||||
|
||||
@Test
|
||||
void testSha256Hex() {
|
||||
String sign = CryptoUtils.sha256Hex("Hello world.");
|
||||
assertEquals("aa3ec16e6acc809d8b2818662276256abfd2f1b441cb51574933f3d4bd115d11", sign);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHmacSha256Base64Debug() {
|
||||
String signature = CryptoUtils.hmacSha256Base64("your-real-key", "your-real-data");
|
||||
assertEquals("8JrfX0v5Tt3s8PfI85o6jcf5XM3C+vLlMwvFp45LupU=", signature);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHmacSha256Hex() {
|
||||
String signature = CryptoUtils.hmacSha256Hex("your-real-key", "your-real-data");;
|
||||
assertEquals("41878ccd7ecd795a2dd7ec39be7f33fed4be3ec75f5307689e39dd6f41fdbaac", signature);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,6 +19,7 @@ package org.apache.hertzbeat.alert.util;
|
||||
|
||||
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 java.util.Optional;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -58,4 +59,13 @@ class DateUtilTest {
|
||||
actualTimestamp = DateUtil.getTimeStampFromFormat(date, format);
|
||||
assertFalse(actualTimestamp.isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getZonedTimeStampFromFormat() {
|
||||
String dataStr = "2025/06/02 22:56:15 GMT+08:00";
|
||||
Long time = DateUtil.getZonedTimeStampFromFormat(dataStr, "yyyy/MM/dd HH:mm:ss 'GMT'XXX");
|
||||
assertNotNull(time);
|
||||
assertEquals(1748876175000L, time);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# 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.
|
||||
wrapperVersion=3.3.2
|
||||
distributionType=only-script
|
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip
|
||||
@@ -69,8 +69,8 @@
|
||||
</dependency>
|
||||
<!-- mysql -->
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<!-- clickhouse -->
|
||||
@@ -164,5 +164,11 @@
|
||||
<groupId>org.apache.sshd</groupId>
|
||||
<artifactId>sshd-sftp</artifactId>
|
||||
</dependency>
|
||||
<!-- Zookeeper -->
|
||||
<dependency>
|
||||
<groupId>org.apache.zookeeper</groupId>
|
||||
<artifactId>zookeeper</artifactId>
|
||||
<version>${zookeeper.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
+156
-8
@@ -17,6 +17,7 @@
|
||||
|
||||
package org.apache.hertzbeat.collector.collect.database;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.ResultSet;
|
||||
@@ -74,6 +75,7 @@ public class JdbcCommonCollect extends AbstractCollect {
|
||||
// code execution related - may result in remote code execution
|
||||
"init=", "javaobjectserializer=", "runscript", "serverstatusdiffinterceptor",
|
||||
"queryinterceptors=", "statementinterceptors=", "exceptioninterceptors=",
|
||||
"xp_cmdshell", "create function", "dbms_java", "sp_sysexecute", "load_file",
|
||||
|
||||
// multiple statement execution - may lead to SQL injection
|
||||
"allowmultiqueries",
|
||||
@@ -82,6 +84,96 @@ public class JdbcCommonCollect extends AbstractCollect {
|
||||
"autodeserialize", "detectcustomcollations",
|
||||
};
|
||||
|
||||
// universal bypass detection mode - applicable to all databases for dangerous command bypass detection
|
||||
private static final String[] UNIVERSAL_BYPASS_PATTERNS = {
|
||||
".*create\\s*([/\\\\]|\\\\n|/n|\\n)\\s*trigger.*",
|
||||
".*create\\s*([/\\\\]|\\\\n|/n|\\n)\\s*function.*",
|
||||
".*drop\\s*([/\\\\]|\\\\n|/n|\\n)\\s*table.*",
|
||||
".*drop\\s*([/\\\\]|\\\\n|/n|\\n)\\s*database.*",
|
||||
".*run\\s*([/\\\\]|\\\\n|/n|\\n)\\s*script.*",
|
||||
".*alter\\s*([/\\\\]|\\\\n|/n|\\n)\\s*system.*",
|
||||
".*grant\\s*([/\\\\]|\\\\n|/n|\\n)\\s*all.*",
|
||||
".*revoke\\s*([/\\\\]|\\\\n|/n|\\n)\\s*all.*",
|
||||
".*xp\\s*([/\\\\]|\\\\n|/n|\\n)\\s*cmdshell.*",
|
||||
".*load\\s*([/\\\\]|\\\\n|/n|\\n)\\s*file.*"
|
||||
};
|
||||
|
||||
// database platform specific bypass detection mode
|
||||
private static final HashMap<String, String[]> PLATFORM_BYPASS_PATTERNS = new HashMap<>();
|
||||
|
||||
static {
|
||||
// H2 database special character bypass mode
|
||||
PLATFORM_BYPASS_PATTERNS.put("h2", new String[]{
|
||||
".*(\\\\\\\\|/|\\\\|\\\\n|/n|\\n)\\s*init\\s*=.*",
|
||||
".*in\\s*([/\\\\]|\\\\n|/n|\\n)\\s*it\\s*=.*",
|
||||
".*(\\\\\\\\|/|\\\\|\\\\n|/n|\\n)\\s*runscript\\s+from.*",
|
||||
".*ru\\s*([/\\\\]|\\\\n|/n|\\n)\\s*script\\s+from.*"
|
||||
});
|
||||
|
||||
// MySQL/MariaDB bypass mode
|
||||
String[] mysqlPatterns = {
|
||||
".*allow\\s*([/\\\\]|\\\\n|/n|\\n)\\s*load\\s*([/\\\\]|\\\\n|/n|\\n)\\s*local\\s*([/\\\\]|\\\\n|/n|\\n)\\s*infile.*",
|
||||
".*allow\\s*([/\\\\]|\\\\n|/n|\\n)\\s*multi\\s*([/\\\\]|\\\\n|/n|\\n)\\s*queries.*",
|
||||
".*query\\s*([/\\\\]|\\\\n|/n|\\n)\\s*interceptors.*",
|
||||
".*statement\\s*([/\\\\]|\\\\n|/n|\\n)\\s*interceptors.*",
|
||||
".*exception\\s*([/\\\\]|\\\\n|/n|\\n)\\s*interceptors.*",
|
||||
".*auto\\s*([/\\\\]|\\\\n|/n|\\n)\\s*deserialize.*"
|
||||
};
|
||||
PLATFORM_BYPASS_PATTERNS.put("mysql", mysqlPatterns);
|
||||
PLATFORM_BYPASS_PATTERNS.put("mariadb", mysqlPatterns);
|
||||
|
||||
// PostgreSQL bypass mode
|
||||
PLATFORM_BYPASS_PATTERNS.put("postgresql", new String[]{
|
||||
".*socket\\s*([/\\\\]|\\\\n|/n|\\n)\\s*factory.*",
|
||||
".*logger\\s*([/\\\\]|\\\\n|/n|\\n)\\s*file.*",
|
||||
".*ssl\\s*([/\\\\]|\\\\n|/n|\\n)\\s*mode.*",
|
||||
".*logger\\s*([/\\\\]|\\\\n|/n|\\n)\\s*level.*"
|
||||
});
|
||||
|
||||
// SQL Server bypass mode
|
||||
PLATFORM_BYPASS_PATTERNS.put("sqlserver", new String[]{
|
||||
".*integrated\\s*([/\\\\]|\\\\n|/n|\\n)\\s*security.*",
|
||||
".*authentication\\s*([/\\\\]|\\\\n|/n|\\n)\\s*scheme.*",
|
||||
".*select\\s*([/\\\\]|\\\\n|/n|\\n)\\s*method.*",
|
||||
".*send\\s*([/\\\\]|\\\\n|/n|\\n)\\s*string\\s*([/\\\\]|\\\\n|/n|\\n)\\s*parameters\\s*([/\\\\]|\\\\n|/n|\\n)\\s*as\\s*([/\\\\]|\\\\n|/n|\\n)\\s*unicode.*",
|
||||
".*x\\s*([/\\\\]|\\\\n|/n|\\n)\\s*open\\s*([/\\\\]|\\\\n|/n|\\n)\\s*state.*",
|
||||
".*application\\s*([/\\\\]|\\\\n|/n|\\n)\\s*intent.*"
|
||||
});
|
||||
|
||||
// ClickHouse bypass mode
|
||||
PLATFORM_BYPASS_PATTERNS.put("clickhouse", new String[]{
|
||||
".*custom\\s*([/\\\\]|\\\\n|/n|\\n)\\s*http\\s*([/\\\\]|\\\\n|/n|\\n)\\s*params.*",
|
||||
".*http\\s*([/\\\\]|\\\\n|/n|\\n)\\s*connection\\s*([/\\\\]|\\\\n|/n|\\n)\\s*provider.*",
|
||||
".*check\\s*([/\\\\]|\\\\n|/n|\\n)\\s*all\\s*([/\\\\]|\\\\n|/n|\\n)\\s*nodes.*",
|
||||
".*fail\\s*([/\\\\]|\\\\n|/n|\\n)\\s*over.*",
|
||||
".*use\\s*([/\\\\]|\\\\n|/n|\\n)\\s*objects\\s*([/\\\\]|\\\\n|/n|\\n)\\s*in\\s*([/\\\\]|\\\\n|/n|\\n)\\s*arrays.*"
|
||||
});
|
||||
|
||||
// Oracle bypass mode
|
||||
PLATFORM_BYPASS_PATTERNS.put("oracle", new String[]{
|
||||
".*oracle\\s*([/\\\\]|\\\\n|/n|\\n)\\s*jdbc.*",
|
||||
".*oracle\\s*([/\\\\]|\\\\n|/n|\\n)\\s*net.*",
|
||||
".*oracle\\.jdbc\\.timezoneinfotable\\s*=.*",
|
||||
".*oracle\\.net\\.wallet_location\\s*=.*",
|
||||
".*oracle\\.net\\.ssl_server_dn_match\\s*=\\s*false.*",
|
||||
".*oracle\\.jdbc\\.enablesqlinjectionattack\\s*=\\s*true.*",
|
||||
".*oracle\\.jdbc\\.implicitstatementcachesize\\s*=\\s*0.*",
|
||||
".*oracle\\.jdbc\\.timezoneinfotable\\s*=.*",
|
||||
".*oracle\\.net\\.wallet_location\\s*=.*",
|
||||
".*oracle\\.net\\.ssl_server_dn_match\\s*=\\s*false.*",
|
||||
".*oracle\\.jdbc\\.enablesqlinjectionattack\\s*=\\s*true.*",
|
||||
".*oracle\\.jdbc\\.implicitstatementcachesize\\s*=\\s*0.*"
|
||||
});
|
||||
|
||||
// DM bypass mode
|
||||
PLATFORM_BYPASS_PATTERNS.put("dm", new String[]{
|
||||
".*login\\s*([/\\\\]|\\\\n|/n|\\n)\\s*mode.*",
|
||||
".*compatible\\s*([/\\\\]|\\\\n|/n|\\n)\\s*mode.*",
|
||||
".*en\\s*([/\\\\]|\\\\n|/n|\\n)\\s*crypt.*",
|
||||
".*ci\\s*([/\\\\]|\\\\n|/n|\\n)\\s*pher.*"
|
||||
});
|
||||
}
|
||||
|
||||
private final GlobalConnectionCache connectionCommonCache = GlobalConnectionCache.getInstance();
|
||||
|
||||
|
||||
@@ -91,8 +183,9 @@ public class JdbcCommonCollect extends AbstractCollect {
|
||||
throw new IllegalArgumentException("Database collect must has jdbc params");
|
||||
}
|
||||
if (StringUtils.hasText(metrics.getJdbc().getUrl())) {
|
||||
String url = metrics.getJdbc().getUrl().toLowerCase();
|
||||
for (String keyword : VULNERABLE_KEYWORDS) {
|
||||
if (metrics.getJdbc().getUrl().contains(keyword)) {
|
||||
if (url.contains(keyword.toLowerCase())) {
|
||||
throw new IllegalArgumentException("Jdbc url prohibit contains vulnerable param " + keyword);
|
||||
}
|
||||
}
|
||||
@@ -340,6 +433,27 @@ public class JdbcCommonCollect extends AbstractCollect {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively decode the URL to prevent multiple encoding bypasses.
|
||||
*
|
||||
* @param url jdbc url
|
||||
* @return decoded decoded jdbc url
|
||||
*/
|
||||
private String recursiveDecode(String url) {
|
||||
String prev;
|
||||
String decoded = url;
|
||||
int max = 5; // Decode it at most 5 times to prevent infinite loops.
|
||||
do {
|
||||
prev = decoded;
|
||||
try {
|
||||
decoded = java.net.URLDecoder.decode(prev, StandardCharsets.UTF_8);
|
||||
} catch (Exception e) {
|
||||
break;
|
||||
}
|
||||
} while (!prev.equals(decoded) && --max > 0);
|
||||
return decoded;
|
||||
}
|
||||
|
||||
/**
|
||||
* construct jdbc url due the jdbc protocol
|
||||
*
|
||||
@@ -355,20 +469,54 @@ public class JdbcCommonCollect extends AbstractCollect {
|
||||
throw new IllegalArgumentException("JDBC URL length exceeds maximum limit of 2048 characters");
|
||||
}
|
||||
// remove special characters
|
||||
String cleanedUrl = jdbcProtocol.getUrl().replaceAll("[\\x00-\\x1F\\x7F]", "");
|
||||
String url = cleanedUrl.toLowerCase();
|
||||
String cleanedUrl = jdbcProtocol.getUrl().replaceAll("[\\x00-\\x1F\\x7F\\xA0]", "");
|
||||
String url = recursiveDecode(cleanedUrl);
|
||||
url = url.toLowerCase();
|
||||
// url format check
|
||||
if (!url.matches("^jdbc:[a-zA-Z0-9]+:([^\\s;]+)(;[^\\s;]+)*$")) {
|
||||
throw new IllegalArgumentException("Invalid JDBC URL format");
|
||||
}
|
||||
// backlist check
|
||||
for (String keyword : BLACK_LIST) {
|
||||
if (url.contains(keyword)) {
|
||||
if (url.contains(keyword.toLowerCase())) {
|
||||
throw new IllegalArgumentException("Invalid JDBC URL: contains potentially malicious parameter: " + keyword);
|
||||
}
|
||||
}
|
||||
// url format check
|
||||
if (!url.matches("^jdbc:[a-zA-Z0-9]+://[^\\s]+$")) {
|
||||
throw new IllegalArgumentException("Invalid JDBC URL format");
|
||||
// universal detection
|
||||
String normalizedUrl = url.replaceAll("[\\x00-\\x1F\\x7F\\xA0]", " ").toLowerCase();
|
||||
// universal detection of JDBC injection and deserialization attacks
|
||||
if (normalizedUrl.matches(".*jndi\\s*[:=].*")
|
||||
|| normalizedUrl.matches(".*ldap\\s*[:=].*")
|
||||
|| normalizedUrl.matches(".*rmi\\s*[:=].*")
|
||||
|| normalizedUrl.matches(".*java\\s*[:=].*")
|
||||
|| normalizedUrl.matches(".*serialization\\s*[:=].*")
|
||||
|| normalizedUrl.matches(".*deserializ.*\\s*[:=].*")
|
||||
|| normalizedUrl.matches(".*objectinputstream\\s*[:=].*")
|
||||
|| normalizedUrl.matches(".*readobject\\s*[:=].*")) {
|
||||
throw new IllegalArgumentException("Invalid JDBC URL: contains potentially malicious JNDI or deserialization parameter");
|
||||
}
|
||||
return cleanedUrl;
|
||||
// universal detection of bypass
|
||||
for (String pattern : UNIVERSAL_BYPASS_PATTERNS) {
|
||||
if (normalizedUrl.matches(pattern)) {
|
||||
throw new IllegalArgumentException("Invalid JDBC URL: contains potentially malicious bypass pattern");
|
||||
}
|
||||
}
|
||||
// database platform specific bypass detection
|
||||
if (jdbcProtocol.getPlatform() != null) {
|
||||
String platform = jdbcProtocol.getPlatform().toLowerCase();
|
||||
// check for specific bypass modes on the platform
|
||||
String[] platformPatterns = PLATFORM_BYPASS_PATTERNS.get(platform);
|
||||
if (platformPatterns != null) {
|
||||
for (String pattern : platformPatterns) {
|
||||
if (normalizedUrl.matches(pattern)) {
|
||||
throw new IllegalArgumentException("Invalid " + platform.toUpperCase() + " JDBC URL: contains potentially malicious bypass pattern");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return normalizedUrl;
|
||||
}
|
||||
assert jdbcProtocol.getPlatform() != null;
|
||||
return switch (jdbcProtocol.getPlatform()) {
|
||||
case "mysql", "mariadb" -> "jdbc:mysql://" + host + ":" + port
|
||||
+ "/" + (jdbcProtocol.getDatabase() == null ? "" : jdbcProtocol.getDatabase())
|
||||
|
||||
+7
-3
@@ -166,7 +166,7 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
case DispatchConstants.PARSE_XML_PATH ->
|
||||
parseResponseByXmlPath(resp, metrics, builder, responseTime);
|
||||
case DispatchConstants.PARSE_WEBSITE ->
|
||||
parseResponseByWebsite(resp, metrics, metrics.getHttp(), builder, responseTime);
|
||||
parseResponseByWebsite(resp, metrics, metrics.getHttp(), builder, responseTime, statusCode);
|
||||
case DispatchConstants.PARSE_SITE_MAP ->
|
||||
parseResponseBySiteMap(resp, metrics.getAliasFields(), builder);
|
||||
case DispatchConstants.PARSE_HEADER ->
|
||||
@@ -237,11 +237,15 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
}
|
||||
|
||||
private void parseResponseByWebsite(String resp, Metrics metrics, HttpProtocol http,
|
||||
CollectRep.MetricsData.Builder builder, Long responseTime) {
|
||||
CollectRep.MetricsData.Builder builder, Long responseTime, int statusCode) {
|
||||
CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
|
||||
int keywordNum = CollectUtil.countMatchKeyword(resp, http.getKeyword());
|
||||
for (String alias : metrics.getAliasFields()) {
|
||||
addColumnForSummary(responseTime, valueRowBuilder, keywordNum, alias);
|
||||
if (CollectorConstants.STATUS_CODE.equalsIgnoreCase(alias)) {
|
||||
valueRowBuilder.addColumn(Integer.toString(statusCode));
|
||||
} else {
|
||||
addColumnForSummary(responseTime, valueRowBuilder, keywordNum, alias);
|
||||
}
|
||||
}
|
||||
builder.addValueRow(valueRowBuilder.build());
|
||||
}
|
||||
|
||||
+97
-12
@@ -66,7 +66,7 @@ public class JmxCollectImpl extends AbstractCollect {
|
||||
private static final String JMX_URL_PREFIX = "service:jmx:rmi:///jndi/rmi://";
|
||||
|
||||
private static final String JMX_URL_SUFFIX = "/jmxrmi";
|
||||
|
||||
|
||||
private static final String IGNORED_STUB = "/stub/";
|
||||
|
||||
private static final String SUB_ATTRIBUTE = "->";
|
||||
@@ -75,7 +75,6 @@ public class JmxCollectImpl extends AbstractCollect {
|
||||
|
||||
private final ClassLoader jmxClassLoader;
|
||||
|
||||
|
||||
public JmxCollectImpl() {
|
||||
jmxClassLoader = new JmxClassLoader(ClassLoader.getSystemClassLoader());
|
||||
}
|
||||
@@ -83,13 +82,66 @@ public class JmxCollectImpl extends AbstractCollect {
|
||||
@Override
|
||||
public void preCheck(Metrics metrics) throws IllegalArgumentException {
|
||||
Assert.isTrue(metrics != null && metrics.getJmx() != null, "JMX collect must have JMX params");
|
||||
JmxProtocol jmxProtocol = metrics.getJmx();
|
||||
|
||||
String url = metrics.getJmx().getUrl();
|
||||
// Validate JMX URL if provided
|
||||
String url = jmxProtocol.getUrl();
|
||||
if (StringUtils.hasText(url)) {
|
||||
Assert.doesNotContain(url, IGNORED_STUB, "JMX url prohibit contains stub, please check");
|
||||
|
||||
// Prevent JNDI injection by validating URL format
|
||||
validateJmxUrl(url);
|
||||
} else {
|
||||
// Validate host and port inputs
|
||||
String host = jmxProtocol.getHost();
|
||||
int port = Integer.parseInt(jmxProtocol.getPort());
|
||||
|
||||
// Validate host format (only allow valid hostnames or IP addresses)
|
||||
Assert.isTrue(isValidHostname(host), "Invalid hostname format");
|
||||
Assert.isTrue(port > 0 && port <= 65535, "Port must be between 1 and 65535");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate JMX URL
|
||||
*
|
||||
* @param url JMX URL to validate
|
||||
* @throws IllegalArgumentException if URL is potentially malicious
|
||||
*/
|
||||
private void validateJmxUrl(String url) throws IllegalArgumentException {
|
||||
// Only allow service:jmx:rmi protocol
|
||||
Assert.isTrue(url.startsWith("service:jmx:rmi:"), "Only service:jmx:rmi protocol is supported");
|
||||
|
||||
String[] disallowedPatterns = { "ldap:", "rmi:", "iiop:", "nis:", "dns:", "corbaname:", "http:", "https:" };
|
||||
for (String pattern : disallowedPatterns) {
|
||||
if (url.contains(pattern) && !pattern.equals("rmi:///jndi/rmi:")) {
|
||||
throw new IllegalArgumentException("Potentially unsafe JNDI protocol detected in URL: " + pattern);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for suspicious patterns
|
||||
if (url.contains("${") || url.contains("$[") || url.contains(":#") || url.contains(":/")) {
|
||||
throw new IllegalArgumentException("Potentially malicious pattern detected in JMX URL");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate hostname format
|
||||
*
|
||||
* @param hostname Hostname to validate
|
||||
* @return true if hostname is valid
|
||||
*/
|
||||
private boolean isValidHostname(String hostname) {
|
||||
if (hostname == null || hostname.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Simplified hostname/IP validation regex
|
||||
// This regex accepts valid hostnames, IPv4 and IPv6 addresses
|
||||
String hostnameRegex = "^([a-zA-Z0-9][-a-zA-Z0-9]*\\.)+[a-zA-Z0-9][-a-zA-Z0-9]*$|^(\\d{1,3}\\.){3}\\d{1,3}$|^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$";
|
||||
return hostname.matches(hostnameRegex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void collect(CollectRep.MetricsData.Builder builder, Metrics metrics) {
|
||||
ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();
|
||||
@@ -172,16 +224,18 @@ public class JmxCollectImpl extends AbstractCollect {
|
||||
log.info("attribute {} value is null.", attribute.getName());
|
||||
continue;
|
||||
}
|
||||
if (value instanceof Number || value instanceof String || value instanceof ObjectName
|
||||
if (value instanceof Number || value instanceof String || value instanceof ObjectName
|
||||
|| value instanceof Boolean || value instanceof Date || value instanceof TimeUnit) {
|
||||
attributeValueMap.put(attribute.getName(), value.toString());
|
||||
} else if (value instanceof CompositeData compositeData) {
|
||||
} else if (value instanceof CompositeData) {
|
||||
CompositeData compositeData = (CompositeData) value;
|
||||
CompositeType compositeType = compositeData.getCompositeType();
|
||||
for (String typeKey : compositeType.keySet()) {
|
||||
Object fieldValue = compositeData.get(typeKey);
|
||||
attributeValueMap.put(attribute.getName() + SUB_ATTRIBUTE + typeKey, fieldValue.toString());
|
||||
}
|
||||
} else if (value instanceof String[] values) {
|
||||
} else if (value instanceof String[]) {
|
||||
String[] values = (String[]) value;
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (int index = 0; index < values.length; index++) {
|
||||
builder.append(values[index]);
|
||||
@@ -219,12 +273,33 @@ public class JmxCollectImpl extends AbstractCollect {
|
||||
String url;
|
||||
if (jmxProtocol.getUrl() != null) {
|
||||
url = jmxProtocol.getUrl();
|
||||
// Double check URL format for security
|
||||
if (!url.startsWith("service:jmx:rmi:")) {
|
||||
throw new IOException("Unsupported JMX URL protocol. Only service:jmx:rmi: is allowed.");
|
||||
}
|
||||
} else {
|
||||
url = JMX_URL_PREFIX + jmxProtocol.getHost() + ":" + jmxProtocol.getPort() + JMX_URL_SUFFIX;
|
||||
// More strict formatting with proper escaping
|
||||
String host = jmxProtocol.getHost();
|
||||
int port = Integer.parseInt(jmxProtocol.getPort());
|
||||
|
||||
// Additional validation at connection time
|
||||
if (!isValidHostname(host)) {
|
||||
throw new IOException("Invalid hostname format for JMX connection: " + host);
|
||||
}
|
||||
if (port <= 0 || port > 65535) {
|
||||
throw new IOException("Invalid port for JMX connection: " + port);
|
||||
}
|
||||
|
||||
url = JMX_URL_PREFIX + host + ":" + port + JMX_URL_SUFFIX;
|
||||
}
|
||||
|
||||
// Set security properties to prevent remote class loading
|
||||
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "false");
|
||||
System.setProperty("com.sun.jndi.cosnaming.object.trustURLCodebase", "false");
|
||||
|
||||
Map<String, Object> environment = new HashMap<>(4);
|
||||
if (StringUtils.hasText(jmxProtocol.getUsername()) && StringUtils.hasText(jmxProtocol.getPassword())) {
|
||||
String[] credential = new String[] {jmxProtocol.getUsername(), jmxProtocol.getPassword()};
|
||||
String[] credential = new String[] { jmxProtocol.getUsername(), jmxProtocol.getPassword() };
|
||||
environment.put(javax.management.remote.JMXConnector.CREDENTIALS, credential);
|
||||
}
|
||||
if (Boolean.TRUE.toString().equals(jmxProtocol.getSsl())) {
|
||||
@@ -233,10 +308,20 @@ public class JmxCollectImpl extends AbstractCollect {
|
||||
environment.put(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE, clientSocketFactory);
|
||||
environment.put("com.sun.jndi.rmi.factory.socket", clientSocketFactory);
|
||||
}
|
||||
JMXServiceURL jmxServiceUrl = new JMXServiceURL(url);
|
||||
conn = JMXConnectorFactory.connect(jmxServiceUrl, environment);
|
||||
connectionCommonCache.addCache(identifier, new JmxConnect(conn));
|
||||
return conn;
|
||||
|
||||
// Limit JMX connection timeout
|
||||
environment.put("jmx.remote.x.client.connection.timeout", 10000);
|
||||
environment.put("jmx.remote.x.server.connection.timeout", 10000);
|
||||
|
||||
try {
|
||||
JMXServiceURL jmxServiceUrl = new JMXServiceURL(url);
|
||||
conn = JMXConnectorFactory.connect(jmxServiceUrl, environment);
|
||||
connectionCommonCache.addCache(identifier, new JmxConnect(conn));
|
||||
return conn;
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to connect to JMX server: {}", e.getMessage());
|
||||
throw new IOException("Failed to connect to JMX server: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+4
-2
@@ -72,12 +72,13 @@ import org.springframework.util.StringUtils;
|
||||
* prometheus auto collect
|
||||
*/
|
||||
@Slf4j
|
||||
public class PrometheusAutoCollectImpl {
|
||||
public class PrometheusAutoCollectImpl implements PrometheusCollect {
|
||||
|
||||
private final Set<Integer> defaultSuccessStatusCodes = Stream.of(HttpStatus.SC_OK, HttpStatus.SC_CREATED,
|
||||
HttpStatus.SC_ACCEPTED, HttpStatus.SC_MULTIPLE_CHOICES, HttpStatus.SC_MOVED_PERMANENTLY,
|
||||
HttpStatus.SC_MOVED_TEMPORARILY).collect(Collectors.toSet());
|
||||
|
||||
@Override
|
||||
public List<CollectRep.MetricsData> collect(CollectRep.MetricsData.Builder builder,
|
||||
Metrics metrics) {
|
||||
try {
|
||||
@@ -139,6 +140,7 @@ public class PrometheusAutoCollectImpl {
|
||||
return Collections.singletonList(builder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String supportProtocol() {
|
||||
return DispatchConstants.PROTOCOL_PROMETHEUS;
|
||||
}
|
||||
@@ -179,7 +181,7 @@ public class PrometheusAutoCollectImpl {
|
||||
builder.addField(CollectRep.Field.newBuilder().setName(label.getName())
|
||||
.setType(CommonConstants.TYPE_STRING).setLabel(true).build());
|
||||
});
|
||||
builder.addField(CollectRep.Field.newBuilder().setName("value")
|
||||
builder.addField(CollectRep.Field.newBuilder().setName("metric_value")
|
||||
.setType(CommonConstants.TYPE_NUMBER).setLabel(false).build());
|
||||
}
|
||||
Map<String, String> labelMap = metric.getLabels()
|
||||
|
||||
+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.collector.collect.prometheus;
|
||||
|
||||
import org.apache.hertzbeat.common.entity.job.Metrics;
|
||||
import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Prometheus metrics collector interface
|
||||
*/
|
||||
public interface PrometheusCollect {
|
||||
|
||||
/**
|
||||
* Collect prometheus metrics data
|
||||
* @param builder metrics data builder
|
||||
* @param metrics metrics config
|
||||
* @return list of metrics data
|
||||
*/
|
||||
List<CollectRep.MetricsData> collect(CollectRep.MetricsData.Builder builder, Metrics metrics);
|
||||
|
||||
/**
|
||||
* Get the protocol name this collector supported
|
||||
* @return protocol name
|
||||
*/
|
||||
String supportProtocol();
|
||||
}
|
||||
+308
@@ -0,0 +1,308 @@
|
||||
/*
|
||||
* 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.collector.collect.prometheus;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.collector.collect.common.http.CommonHttpClient;
|
||||
import org.apache.hertzbeat.collector.dispatch.DispatchConstants;
|
||||
import org.apache.hertzbeat.collector.util.CollectUtil;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.constants.NetworkConstants;
|
||||
import org.apache.hertzbeat.common.constants.SignConstants;
|
||||
import org.apache.hertzbeat.common.entity.job.Metrics;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.PrometheusProtocol;
|
||||
import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
import org.apache.hertzbeat.common.util.Base64Util;
|
||||
import org.apache.hertzbeat.common.util.CommonUtil;
|
||||
import org.apache.hertzbeat.common.util.IpDomainUtil;
|
||||
import org.apache.http.HttpHeaders;
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.apache.http.auth.AuthScope;
|
||||
import org.apache.http.auth.UsernamePasswordCredentials;
|
||||
import org.apache.http.client.AuthCache;
|
||||
import org.apache.http.client.ClientProtocolException;
|
||||
import org.apache.http.client.CredentialsProvider;
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpUriRequest;
|
||||
import org.apache.http.client.methods.RequestBuilder;
|
||||
import org.apache.http.client.protocol.HttpClientContext;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.auth.DigestScheme;
|
||||
import org.apache.http.impl.client.BasicAuthCache;
|
||||
import org.apache.http.impl.client.BasicCredentialsProvider;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.net.ConnectException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import javax.net.ssl.SSLException;
|
||||
|
||||
import static org.apache.hertzbeat.common.constants.SignConstants.RIGHT_DASH;
|
||||
|
||||
|
||||
@Slf4j
|
||||
public class PrometheusProxyCollectImpl implements PrometheusCollect {
|
||||
|
||||
private final Set<Integer> defaultSuccessStatusCodes = Stream.of(HttpStatus.SC_OK, HttpStatus.SC_CREATED,
|
||||
HttpStatus.SC_ACCEPTED, HttpStatus.SC_MULTIPLE_CHOICES, HttpStatus.SC_MOVED_PERMANENTLY,
|
||||
HttpStatus.SC_MOVED_TEMPORARILY).collect(Collectors.toSet());
|
||||
|
||||
public static final String RAW_TEXT_CONTENT_FIELD_NAME = "raw_text_content";
|
||||
|
||||
@Override
|
||||
public List<CollectRep.MetricsData> collect(CollectRep.MetricsData.Builder builder, Metrics metrics) {
|
||||
PrometheusProtocol prometheusProtocol = metrics.getPrometheus();
|
||||
HttpUriRequest request;
|
||||
try {
|
||||
validateParams(metrics);
|
||||
} catch (Exception e) {
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg(e.getMessage());
|
||||
return Collections.singletonList(builder.build());
|
||||
}
|
||||
|
||||
HttpContext httpContext = createHttpContext(prometheusProtocol);
|
||||
request = createHttpRequest(prometheusProtocol);
|
||||
|
||||
try (CloseableHttpResponse response = CommonHttpClient.getHttpClient().execute(request, httpContext)) {
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
log.debug("Prometheus proxy collect, response status: {}", statusCode);
|
||||
|
||||
if (!defaultSuccessStatusCodes.contains(statusCode)) {
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg(NetworkConstants.STATUS_CODE + SignConstants.BLANK + statusCode);
|
||||
return Collections.singletonList(builder.build());
|
||||
}
|
||||
|
||||
String rawTextContent = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
|
||||
|
||||
builder.clearFields();
|
||||
builder.clearValues();
|
||||
|
||||
CollectRep.Field rawDataField = CollectRep.Field.newBuilder()
|
||||
.setName(RAW_TEXT_CONTENT_FIELD_NAME)
|
||||
.setType(CommonConstants.TYPE_STRING)
|
||||
.build();
|
||||
builder.addField(rawDataField);
|
||||
|
||||
CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
|
||||
valueRowBuilder.addColumn(rawTextContent);
|
||||
builder.addValueRow(valueRowBuilder.build());
|
||||
|
||||
builder.setCode(CollectRep.Code.SUCCESS);
|
||||
} catch (ClientProtocolException e1) {
|
||||
String errorMsg = CommonUtil.getMessageFromThrowable(e1);
|
||||
log.error("Prometheus proxy collect error: {}. Host: {}, Port: {}", errorMsg, prometheusProtocol.getHost(), prometheusProtocol.getPort(), e1);
|
||||
builder.setCode(CollectRep.Code.UN_CONNECTABLE);
|
||||
builder.setMsg(errorMsg);
|
||||
} catch (UnknownHostException e2) {
|
||||
String errorMsg = CommonUtil.getMessageFromThrowable(e2);
|
||||
log.info("Prometheus proxy collect unknown host: {}. Host: {}", errorMsg, prometheusProtocol.getHost(), e2);
|
||||
builder.setCode(CollectRep.Code.UN_REACHABLE);
|
||||
builder.setMsg("unknown host:" + errorMsg);
|
||||
} catch (InterruptedIOException | ConnectException | SSLException e3) {
|
||||
String errorMsg = CommonUtil.getMessageFromThrowable(e3);
|
||||
log.info("Prometheus proxy collect connect error: {}. Host: {}, Port: {}", errorMsg, prometheusProtocol.getHost(), prometheusProtocol.getPort(), e3);
|
||||
builder.setCode(CollectRep.Code.UN_CONNECTABLE);
|
||||
builder.setMsg(errorMsg);
|
||||
} catch (IOException e4) {
|
||||
String errorMsg = CommonUtil.getMessageFromThrowable(e4);
|
||||
log.info("Prometheus proxy collect IO error: {}. Host: {}, Port: {}", errorMsg, prometheusProtocol.getHost(), prometheusProtocol.getPort(), e4);
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg(errorMsg);
|
||||
} catch (Exception e) {
|
||||
String errorMsg = CommonUtil.getMessageFromThrowable(e);
|
||||
log.error("Prometheus proxy collect unknown error: {}. Host: {}, Port: {}", errorMsg, prometheusProtocol.getHost(), prometheusProtocol.getPort(), e);
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg(errorMsg);
|
||||
} finally {
|
||||
if (request != null) {
|
||||
request.abort();
|
||||
}
|
||||
}
|
||||
return Collections.singletonList(builder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String supportProtocol() {
|
||||
return DispatchConstants.PROTOCOL_PROMETHEUS;
|
||||
}
|
||||
|
||||
private void validateParams(Metrics metrics) throws Exception {
|
||||
if (metrics == null || metrics.getPrometheus() == null) {
|
||||
throw new Exception("Prometheus collect must has prometheus params");
|
||||
}
|
||||
PrometheusProtocol protocol = metrics.getPrometheus();
|
||||
if (!StringUtils.hasText(protocol.getHost())
|
||||
|| !StringUtils.hasText(protocol.getPort())) {
|
||||
throw new Exception("Prometheus collect must has host and port params");
|
||||
}
|
||||
if (protocol.getPath() == null
|
||||
|| !StringUtils.hasText(protocol.getPath())
|
||||
|| !protocol.getPath().startsWith(RIGHT_DASH)) {
|
||||
protocol.setPath(protocol.getPath() == null ? RIGHT_DASH : RIGHT_DASH + protocol.getPath().trim());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* create httpContext
|
||||
* This method is adapted from PrometheusAutoCollectImpl
|
||||
* @param protocol prometheus protocol
|
||||
* @return context
|
||||
*/
|
||||
public HttpContext createHttpContext(PrometheusProtocol protocol) {
|
||||
PrometheusProtocol.Authorization auth = protocol.getAuthorization();
|
||||
if (auth != null && DispatchConstants.DIGEST_AUTH.equals(auth.getType())) {
|
||||
HttpClientContext clientContext = new HttpClientContext();
|
||||
if (StringUtils.hasText(auth.getDigestAuthUsername())
|
||||
&& StringUtils.hasText(auth.getDigestAuthPassword())) {
|
||||
CredentialsProvider provider = new BasicCredentialsProvider();
|
||||
UsernamePasswordCredentials credentials =
|
||||
new UsernamePasswordCredentials(auth.getDigestAuthUsername(), auth.getDigestAuthPassword());
|
||||
provider.setCredentials(AuthScope.ANY, credentials);
|
||||
AuthCache authCache = new BasicAuthCache();
|
||||
HttpHost targetHost = new HttpHost(protocol.getHost(), Integer.parseInt(protocol.getPort()));
|
||||
authCache.put(targetHost, new DigestScheme());
|
||||
clientContext.setCredentialsProvider(provider);
|
||||
clientContext.setAuthCache(authCache);
|
||||
return clientContext;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* create http request
|
||||
* This method is adapted from PrometheusAutoCollectImpl
|
||||
* @param protocol http params
|
||||
* @return http uri request
|
||||
*/
|
||||
public HttpUriRequest createHttpRequest(PrometheusProtocol protocol) {
|
||||
RequestBuilder requestBuilder = RequestBuilder.get();
|
||||
// params
|
||||
Map<String, String> params = protocol.getParams();
|
||||
if (params != null && !params.isEmpty()) {
|
||||
for (Map.Entry<String, String> param : params.entrySet()) {
|
||||
if (StringUtils.hasText(param.getValue())) {
|
||||
requestBuilder.addParameter(param.getKey(), param.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
requestBuilder.addHeader(HttpHeaders.CONNECTION, NetworkConstants.KEEP_ALIVE);
|
||||
requestBuilder.addHeader(HttpHeaders.USER_AGENT, NetworkConstants.USER_AGENT);
|
||||
// headers The custom request header is overwritten here
|
||||
Map<String, String> headers = protocol.getHeaders();
|
||||
if (headers != null && !headers.isEmpty()) {
|
||||
for (Map.Entry<String, String> header : headers.entrySet()) {
|
||||
if (StringUtils.hasText(header.getValue())) {
|
||||
requestBuilder.addHeader(CollectUtil.replaceUriSpecialChar(header.getKey()),
|
||||
CollectUtil.replaceUriSpecialChar(header.getValue()));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (headers == null || headers.keySet().stream().noneMatch(HttpHeaders.ACCEPT::equalsIgnoreCase)) {
|
||||
requestBuilder.addHeader(HttpHeaders.ACCEPT, MediaType.TEXT_PLAIN_VALUE + ";version=0.0.4,*/*;q=0.1");
|
||||
}
|
||||
|
||||
if (protocol.getAuthorization() != null) {
|
||||
PrometheusProtocol.Authorization authorization = protocol.getAuthorization();
|
||||
if (DispatchConstants.BEARER_TOKEN.equalsIgnoreCase(authorization.getType())) {
|
||||
if (StringUtils.hasText(authorization.getBearerTokenToken())) {
|
||||
String value = DispatchConstants.BEARER + " " + authorization.getBearerTokenToken();
|
||||
requestBuilder.addHeader(HttpHeaders.AUTHORIZATION, value);
|
||||
}
|
||||
} else if (DispatchConstants.BASIC_AUTH.equals(authorization.getType())) {
|
||||
if (StringUtils.hasText(authorization.getBasicAuthUsername())
|
||||
&& StringUtils.hasText(authorization.getBasicAuthPassword())) {
|
||||
String authStr = authorization.getBasicAuthUsername() + ":" + authorization.getBasicAuthPassword();
|
||||
String encodedAuth = Base64Util.encode(authStr);
|
||||
requestBuilder.addHeader(HttpHeaders.AUTHORIZATION, DispatchConstants.BASIC + " " + encodedAuth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (StringUtils.hasLength(protocol.getPayload())) {
|
||||
requestBuilder.setEntity(new StringEntity(protocol.getPayload(), StandardCharsets.UTF_8));
|
||||
if (headers == null || headers.keySet().stream().noneMatch(HttpHeaders.CONTENT_TYPE::equalsIgnoreCase)) {
|
||||
requestBuilder.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
String uriPath = CollectUtil.replaceUriSpecialChar(protocol.getPath());
|
||||
if (IpDomainUtil.isHasSchema(protocol.getHost())) {
|
||||
requestBuilder.setUri(protocol.getHost() + SignConstants.DOUBLE_MARK + protocol.getPort() + uriPath);
|
||||
} else {
|
||||
String ipAddressType = IpDomainUtil.checkIpAddressType(protocol.getHost());
|
||||
String baseUri = NetworkConstants.IPV6.equals(ipAddressType)
|
||||
? String.format("[%s]:%s%s", protocol.getHost(), protocol.getPort(), uriPath)
|
||||
: String.format("%s:%s%s", protocol.getHost(), protocol.getPort(), uriPath);
|
||||
boolean ssl = Boolean.parseBoolean(protocol.getSsl());
|
||||
if (ssl) {
|
||||
requestBuilder.setUri(NetworkConstants.HTTPS_HEADER + baseUri);
|
||||
} else {
|
||||
requestBuilder.setUri(NetworkConstants.HTTP_HEADER + baseUri);
|
||||
}
|
||||
}
|
||||
|
||||
// custom timeout
|
||||
int timeout = CollectUtil.getTimeout(protocol.getTimeout());
|
||||
if (timeout > 0) {
|
||||
RequestConfig requestConfig = RequestConfig.custom()
|
||||
.setConnectTimeout(timeout)
|
||||
.setSocketTimeout(timeout)
|
||||
.setConnectionRequestTimeout(timeout)
|
||||
.setRedirectsEnabled(true)
|
||||
.build();
|
||||
requestBuilder.setConfig(requestConfig);
|
||||
} else {
|
||||
RequestConfig requestConfig = RequestConfig.custom()
|
||||
.setRedirectsEnabled(true)
|
||||
.build();
|
||||
requestBuilder.setConfig(requestConfig);
|
||||
}
|
||||
return requestBuilder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* get collect instance
|
||||
* @return instance
|
||||
*/
|
||||
public static PrometheusProxyCollectImpl getInstance() {
|
||||
return PrometheusProxyCollectImpl.SingleInstance.INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* static instance
|
||||
*/
|
||||
private static class SingleInstance {
|
||||
private static final PrometheusProxyCollectImpl INSTANCE = new PrometheusProxyCollectImpl();
|
||||
}
|
||||
}
|
||||
+128
-2
@@ -20,24 +20,42 @@ package org.apache.hertzbeat.collector.collect.sd;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.google.common.collect.Lists;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.hertzbeat.collector.collect.AbstractCollect;
|
||||
import org.apache.hertzbeat.collector.collect.common.http.CommonHttpClient;
|
||||
import org.apache.hertzbeat.collector.dispatch.DispatchConstants;
|
||||
import org.apache.hertzbeat.collector.util.CollectUtil;
|
||||
import org.apache.hertzbeat.common.constants.NetworkConstants;
|
||||
import org.apache.hertzbeat.common.constants.SignConstants;
|
||||
import org.apache.hertzbeat.common.entity.job.Metrics;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.HttpProtocol;
|
||||
import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
import org.apache.hertzbeat.common.entity.sd.ConnectionConfig;
|
||||
import org.apache.hertzbeat.common.entity.sd.ServiceDiscoveryResponseEntity;
|
||||
import org.apache.hertzbeat.common.util.Base64Util;
|
||||
import org.apache.hertzbeat.common.util.CommonUtil;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.apache.http.HttpHeaders;
|
||||
import org.apache.http.auth.AuthScope;
|
||||
import org.apache.http.auth.UsernamePasswordCredentials;
|
||||
import org.apache.http.client.CredentialsProvider;
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpUriRequest;
|
||||
import org.apache.http.client.methods.RequestBuilder;
|
||||
import org.apache.http.client.protocol.HttpClientContext;
|
||||
import org.apache.http.impl.client.BasicCredentialsProvider;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.util.UriUtils;
|
||||
|
||||
|
||||
/**
|
||||
@@ -52,9 +70,10 @@ public class HttpSdCollectImpl extends AbstractCollect {
|
||||
@Override
|
||||
public void collect(CollectRep.MetricsData.Builder builder, Metrics metrics) {
|
||||
List<ConnectionConfig> configList = Lists.newArrayList();
|
||||
HttpUriRequest request = RequestBuilder.get().setUri(metrics.getHttp_sd().getUrl()).build();
|
||||
|
||||
try (CloseableHttpResponse response = CommonHttpClient.getHttpClient().execute(request)) {
|
||||
HttpUriRequest request = createHttpRequest(metrics.getHttp_sd());
|
||||
HttpContext httpContext = createHttpContext(metrics.getHttp_sd());
|
||||
try (CloseableHttpResponse response = CommonHttpClient.getHttpClient().execute(request, httpContext)) {
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
if (statusCode != 200) {
|
||||
log.warn("Failed to fetch sd...");
|
||||
@@ -113,4 +132,111 @@ public class HttpSdCollectImpl extends AbstractCollect {
|
||||
public String supportProtocol() {
|
||||
return DispatchConstants.PROTOCOL_HTTP_SD;
|
||||
}
|
||||
|
||||
/**
|
||||
* create httpContext
|
||||
*
|
||||
* @param httpSdProtocol http sd protocol
|
||||
* @return context
|
||||
*/
|
||||
public HttpContext createHttpContext(HttpProtocol httpSdProtocol) {
|
||||
HttpProtocol.Authorization auth = httpSdProtocol.getAuthorization();
|
||||
if (auth != null && DispatchConstants.DIGEST_AUTH.equals(auth.getType())) {
|
||||
HttpClientContext clientContext = new HttpClientContext();
|
||||
if (org.springframework.util.StringUtils.hasText(auth.getDigestAuthUsername())
|
||||
&& org.springframework.util.StringUtils.hasText(auth.getDigestAuthPassword())) {
|
||||
CredentialsProvider provider = new BasicCredentialsProvider();
|
||||
UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(auth.getDigestAuthUsername(),
|
||||
auth.getDigestAuthPassword());
|
||||
URL url;
|
||||
try {
|
||||
url = new URL(httpSdProtocol.getUrl());
|
||||
} catch (MalformedURLException e) {
|
||||
throw new IllegalArgumentException("Invalid URI format.", e);
|
||||
}
|
||||
int port = url.getPort() != -1 ? url.getPort() : ("https".equals(url.getProtocol()) ? 443 : 80);
|
||||
AuthScope authScope = new AuthScope(url.getHost(), port);
|
||||
provider.setCredentials(authScope, credentials);
|
||||
|
||||
clientContext.setCredentialsProvider(provider);
|
||||
return clientContext;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* create http request
|
||||
*
|
||||
* @param httpSdProtocol http request set
|
||||
* @return http uri request
|
||||
*/
|
||||
public HttpUriRequest createHttpRequest(HttpProtocol httpSdProtocol) {
|
||||
RequestBuilder requestBuilder = RequestBuilder.get();
|
||||
|
||||
// The default request header can be overridden if customized
|
||||
// keep-alive
|
||||
requestBuilder.addHeader(HttpHeaders.CONNECTION, NetworkConstants.KEEP_ALIVE);
|
||||
requestBuilder.addHeader(HttpHeaders.USER_AGENT, NetworkConstants.USER_AGENT);
|
||||
// headers The custom request header is overwritten here
|
||||
Map<String, String> headers = httpSdProtocol.getHeaders();
|
||||
if (headers != null && !headers.isEmpty()) {
|
||||
for (Map.Entry<String, String> header : headers.entrySet()) {
|
||||
if (org.springframework.util.StringUtils.hasText(header.getValue())) {
|
||||
requestBuilder.addHeader(header.getKey(), header.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
// add accept
|
||||
requestBuilder.addHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
|
||||
|
||||
// add authorization
|
||||
if (httpSdProtocol.getAuthorization() != null) {
|
||||
HttpProtocol.Authorization authorization = httpSdProtocol.getAuthorization();
|
||||
if (DispatchConstants.BEARER_TOKEN.equalsIgnoreCase(authorization.getType())) {
|
||||
String value = DispatchConstants.BEARER + SignConstants.BLANK + authorization.getBearerTokenToken();
|
||||
requestBuilder.addHeader(HttpHeaders.AUTHORIZATION, value);
|
||||
} else if (DispatchConstants.BASIC_AUTH.equals(authorization.getType())) {
|
||||
if (org.springframework.util.StringUtils.hasText(authorization.getBasicAuthUsername())
|
||||
&& org.springframework.util.StringUtils.hasText(authorization.getBasicAuthPassword())) {
|
||||
String authStr = authorization.getBasicAuthUsername() + SignConstants.DOUBLE_MARK + authorization.getBasicAuthPassword();
|
||||
String encodedAuth = Base64Util.encode(authStr);
|
||||
requestBuilder.addHeader(HttpHeaders.AUTHORIZATION, DispatchConstants.BASIC + SignConstants.BLANK + encodedAuth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// uri encode, default true
|
||||
boolean enableUrlEncoding = Boolean.parseBoolean(httpSdProtocol.getEnableUrlEncoding());
|
||||
if (enableUrlEncoding) {
|
||||
// if the url contains parameters directly
|
||||
if (httpSdProtocol.getUrl().contains("?")) {
|
||||
String path = httpSdProtocol.getUrl().substring(0, httpSdProtocol.getUrl().indexOf("?"));
|
||||
String query = httpSdProtocol.getUrl().substring(httpSdProtocol.getUrl().indexOf("?") + 1);
|
||||
httpSdProtocol.setUrl(UriUtils.encodePath(path, "UTF-8") + "?" + UriUtils.encodeQuery(query, "UTF-8"));
|
||||
} else {
|
||||
httpSdProtocol.setUrl(UriUtils.encodePath(httpSdProtocol.getUrl(), "UTF-8"));
|
||||
}
|
||||
}
|
||||
|
||||
// set uri
|
||||
try {
|
||||
requestBuilder.setUri(httpSdProtocol.getUrl());
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.warn("Invalid URI with illegal characters: {}. User has disabled URL encoding, not applying any encoding.", httpSdProtocol.getUrl());
|
||||
throw e;
|
||||
}
|
||||
|
||||
// custom timeout
|
||||
int timeout = CollectUtil.getTimeout(httpSdProtocol.getTimeout(), 0);
|
||||
if (timeout > 0) {
|
||||
RequestConfig requestConfig = RequestConfig.custom()
|
||||
.setConnectTimeout(timeout)
|
||||
.setSocketTimeout(timeout)
|
||||
.setRedirectsEnabled(true)
|
||||
.build();
|
||||
requestBuilder.setConfig(requestConfig);
|
||||
}
|
||||
return requestBuilder.build();
|
||||
}
|
||||
}
|
||||
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* 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.collector.collect.sd;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.hertzbeat.collector.collect.AbstractCollect;
|
||||
import org.apache.hertzbeat.collector.collect.registry.constant.DiscoveryClientInstance;
|
||||
import org.apache.hertzbeat.collector.collect.registry.discovery.DiscoveryClient;
|
||||
import org.apache.hertzbeat.collector.collect.registry.discovery.DiscoveryClientManagement;
|
||||
import org.apache.hertzbeat.collector.collect.registry.discovery.entity.ServiceInstance;
|
||||
import org.apache.hertzbeat.collector.dispatch.DispatchConstants;
|
||||
import org.apache.hertzbeat.common.entity.job.Metrics;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.RegistryProtocol;
|
||||
import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
import org.apache.hertzbeat.common.util.CommonUtil;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Nacos Service Discovery Collector
|
||||
* This collector integrates with Nacos to discover registered services
|
||||
*/
|
||||
@Slf4j
|
||||
public class NacosSdCollectImpl extends AbstractCollect {
|
||||
|
||||
/**
|
||||
* Client management to interact with discovery services
|
||||
*/
|
||||
private final DiscoveryClientManagement discoveryClientManagement = new DiscoveryClientManagement();
|
||||
|
||||
@Override
|
||||
public void preCheck(Metrics metrics) throws IllegalArgumentException {
|
||||
// Validate the required configuration is present
|
||||
if (metrics == null || metrics.getNacos_sd() == null) {
|
||||
throw new IllegalArgumentException("Nacos service discovery monitoring, the config is null");
|
||||
}
|
||||
if (metrics.getNacos_sd().isInvalid()) {
|
||||
throw new IllegalArgumentException("Nacos service discovery monitoring, the config is invalid");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void collect(CollectRep.MetricsData.Builder builder, Metrics metrics) {
|
||||
// Create Registry protocol from NacosSd protocol
|
||||
RegistryProtocol registryProtocol = RegistryProtocol.builder()
|
||||
.host(metrics.getNacos_sd().getHost())
|
||||
.port(metrics.getNacos_sd().getPort())
|
||||
.discoveryClientTypeName(DiscoveryClientInstance.NACOS.name())
|
||||
.build();
|
||||
|
||||
DiscoveryClient discoveryClient = null;
|
||||
try {
|
||||
// Use the existing NacosDiscoveryClient through DiscoveryClientManagement
|
||||
discoveryClient = discoveryClientManagement.getClient(registryProtocol);
|
||||
if (discoveryClient == null) {
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg("Failed to get Nacos discovery client");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get all services registered in Nacos
|
||||
List<ServiceInstance> services = discoveryClient.getServices();
|
||||
if (CollectionUtils.isEmpty(services)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Populate the response data with service information
|
||||
services.forEach(service -> {
|
||||
CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
|
||||
valueRowBuilder.addColumn(service.getAddress());
|
||||
valueRowBuilder.addColumn(String.valueOf(service.getPort()));
|
||||
valueRowBuilder.addColumn(service.getServiceName());
|
||||
valueRowBuilder.addColumn(service.getHealthStatus());
|
||||
builder.addValueRow(valueRowBuilder.build());
|
||||
});
|
||||
} catch (Exception e) {
|
||||
String errorMsg = CommonUtil.getMessageFromThrowable(e);
|
||||
log.warn("Failed to fetch services from Nacos: {}", errorMsg);
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg(errorMsg);
|
||||
} finally {
|
||||
// Close the discovery client to release resources
|
||||
if (discoveryClient != null) {
|
||||
try {
|
||||
discoveryClient.close();
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to close Nacos discovery client: {}", CommonUtil.getMessageFromThrowable(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String supportProtocol() {
|
||||
return DispatchConstants.PROTOCOL_NACOS_SD;
|
||||
}
|
||||
}
|
||||
+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.collector.collect.sd;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.collector.collect.AbstractCollect;
|
||||
import org.apache.hertzbeat.common.entity.job.Metrics;
|
||||
import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.ZookeeperSdProtocol;
|
||||
import org.apache.hertzbeat.common.entity.sd.ConnectionConfig;
|
||||
import org.apache.zookeeper.KeeperException;
|
||||
import org.apache.zookeeper.ZooKeeper;
|
||||
import org.apache.hertzbeat.common.util.CommonUtil;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* zookeeper sd collector
|
||||
*/
|
||||
@Slf4j
|
||||
public class ZookeeperSdCollectImpl extends AbstractCollect {
|
||||
|
||||
private static int TIMEOUT = 30000;
|
||||
|
||||
@Override
|
||||
public void collect(CollectRep.MetricsData.Builder builder, Metrics metrics) {
|
||||
ZookeeperSdProtocol zookeeperSdProtocol = metrics.getZookeeper_sd();
|
||||
String url = zookeeperSdProtocol.getUrl();
|
||||
String pathPrefix = zookeeperSdProtocol.getPathPrefix();
|
||||
try (ZooKeeper zk = new ZooKeeper(url, TIMEOUT, event -> {})){
|
||||
|
||||
List<String> children = zk.getChildren(pathPrefix, false);
|
||||
List<ConnectionConfig> connectionConfigs = children.stream().map(node -> {
|
||||
String[] split = node.split(":");
|
||||
if (split.length != 2) log.warn("Node format is incorrect: {}, expected format is 'host:port'", node);
|
||||
return new ConnectionConfig(split[0], split[1]);
|
||||
}).toList();
|
||||
connectionConfigs.forEach(config -> {
|
||||
CollectRep.ValueRow valueRow = CollectRep.ValueRow.newBuilder()
|
||||
.addColumn(config.getHost())
|
||||
.addColumn(config.getPort())
|
||||
.build();
|
||||
builder.addValueRow(valueRow);
|
||||
});
|
||||
} catch (IOException e){
|
||||
String errorMsg = CommonUtil.getMessageFromThrowable(e);
|
||||
log.error("Failed to connect to Zookeeper: {}", errorMsg);
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg(errorMsg);
|
||||
} catch (InterruptedException e) {
|
||||
String errorMsg = CommonUtil.getMessageFromThrowable(e);
|
||||
log.error("Zookeeper connection interrupted: {}", errorMsg);
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg(errorMsg);
|
||||
} catch (KeeperException e) {
|
||||
String errorMsg = CommonUtil.getMessageFromThrowable(e);
|
||||
log.error("Zookeeper operation failed: {}", errorMsg);
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg(errorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preCheck(Metrics metrics) throws IllegalArgumentException {
|
||||
if (metrics.getZookeeper_sd() == null) {
|
||||
throw new IllegalArgumentException("Zookeeper SD configuration cannot be null");
|
||||
}
|
||||
if (metrics.getZookeeper_sd().getUrl() == null || metrics.getZookeeper_sd().getUrl().isEmpty()) {
|
||||
throw new IllegalArgumentException("Zookeeper URL cannot be null or empty");
|
||||
}
|
||||
if (metrics.getZookeeper_sd().getPathPrefix() == null || metrics.getZookeeper_sd().getPathPrefix().isEmpty()) {
|
||||
throw new IllegalArgumentException("Zookeeper path prefix cannot be null or empty");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String supportProtocol() {
|
||||
return "zookeeper_sd";
|
||||
}
|
||||
}
|
||||
+26
@@ -17,6 +17,7 @@
|
||||
|
||||
package org.apache.hertzbeat.collector.collect.http;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
@@ -25,6 +26,7 @@ import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import org.apache.hertzbeat.collector.dispatch.DispatchConstants;
|
||||
import org.apache.hertzbeat.common.entity.job.Metrics;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.HttpProtocol;
|
||||
@@ -73,6 +75,30 @@ class HttpCollectImplTest {
|
||||
assert "http".equals(protocol);
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseResponseByWebsite() {
|
||||
HttpProtocol http = HttpProtocol.builder().build();
|
||||
http.setMethod("GET");
|
||||
http.setHost("http://127.0.0.1");
|
||||
http.setUrl("/");
|
||||
http.setPort("8428");
|
||||
http.setParseType("website");
|
||||
http.setEnableUrlEncoding("true");
|
||||
Metrics metrics = Metrics.builder()
|
||||
.http(http)
|
||||
.aliasFields(Lists.newArrayList("responseTime", "keyword", "statusCode"))
|
||||
.build();
|
||||
CollectRep.MetricsData.Builder builder = CollectRep.MetricsData.newBuilder();
|
||||
httpCollectImpl.collect(builder, metrics);
|
||||
|
||||
assertNotNull(builder.getValuesList());
|
||||
for (CollectRep.ValueRow row : builder.getValuesList()) {
|
||||
assertNotNull(row.getColumns(0));
|
||||
assertEquals(row.getColumns(1), "0");
|
||||
assertEquals(row.getColumns(2), "200");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseResponseByXmlPath() throws Exception {
|
||||
// Create a sample XML response
|
||||
|
||||
+200
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* 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.collector.collect.sd;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.nullable;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import org.apache.hertzbeat.collector.collect.common.http.CommonHttpClient;
|
||||
import org.apache.hertzbeat.collector.dispatch.DispatchConstants;
|
||||
import org.apache.hertzbeat.common.entity.job.Metrics;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.HttpProtocol;
|
||||
import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
import org.apache.hertzbeat.common.entity.sd.ServiceDiscoveryResponseEntity;
|
||||
import org.apache.hertzbeat.common.util.CommonUtil;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.ProtocolVersion;
|
||||
import org.apache.http.StatusLine;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpUriRequest;
|
||||
import org.apache.http.entity.ContentType;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.message.BasicStatusLine;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
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.MockedStatic;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
/**
|
||||
* Test case for {@link HttpSdCollectImpl}
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class HttpSdCollectImplTest {
|
||||
|
||||
@InjectMocks
|
||||
private HttpSdCollectImpl httpSdCollectImpl;
|
||||
|
||||
@Mock
|
||||
private CloseableHttpClient httpClient;
|
||||
|
||||
@Mock
|
||||
private CloseableHttpResponse httpResponse;
|
||||
|
||||
private Metrics metrics;
|
||||
private HttpProtocol httpProtocol;
|
||||
private CollectRep.MetricsData.Builder builder;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
httpProtocol = HttpProtocol.builder()
|
||||
.url("http://localhost:8080/services")
|
||||
.build();
|
||||
|
||||
metrics = Metrics.builder()
|
||||
.http_sd(httpProtocol)
|
||||
.build();
|
||||
|
||||
builder = CollectRep.MetricsData.newBuilder();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCollectSuccess() throws IOException {
|
||||
List<ServiceDiscoveryResponseEntity> responseEntities = new ArrayList<>();
|
||||
|
||||
ServiceDiscoveryResponseEntity entity1 = new ServiceDiscoveryResponseEntity();
|
||||
entity1.setTarget(Arrays.asList("192.168.1.10:8080", "192.168.1.11:8081"));
|
||||
|
||||
ServiceDiscoveryResponseEntity entity2 = new ServiceDiscoveryResponseEntity();
|
||||
entity2.setTarget(Arrays.asList("192.168.1.12:8082"));
|
||||
|
||||
responseEntities.add(entity1);
|
||||
responseEntities.add(entity2);
|
||||
|
||||
String jsonResponse = JsonUtil.toJson(responseEntities);
|
||||
|
||||
try (MockedStatic<CommonHttpClient> mockedHttpClient = Mockito.mockStatic(CommonHttpClient.class)) {
|
||||
mockedHttpClient.when(CommonHttpClient::getHttpClient).thenReturn(httpClient);
|
||||
|
||||
when(httpClient.execute(any(HttpUriRequest.class), nullable(HttpContext.class))).thenReturn(httpResponse);
|
||||
|
||||
StatusLine statusLine = new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK");
|
||||
when(httpResponse.getStatusLine()).thenReturn(statusLine);
|
||||
|
||||
HttpEntity entity = new StringEntity(jsonResponse, ContentType.APPLICATION_JSON);
|
||||
when(httpResponse.getEntity()).thenReturn(entity);
|
||||
|
||||
httpSdCollectImpl.collect(builder, metrics);
|
||||
|
||||
assertEquals(3, builder.getValuesCount());
|
||||
|
||||
CollectRep.ValueRow firstRow = builder.getValues(0);
|
||||
assertEquals("192.168.1.10", firstRow.getColumns(0));
|
||||
assertEquals("8080", firstRow.getColumns(1));
|
||||
|
||||
CollectRep.ValueRow secondRow = builder.getValues(1);
|
||||
assertEquals("192.168.1.11", secondRow.getColumns(0));
|
||||
assertEquals("8081", secondRow.getColumns(1));
|
||||
|
||||
CollectRep.ValueRow thirdRow = builder.getValues(2);
|
||||
assertEquals("192.168.1.12", thirdRow.getColumns(0));
|
||||
assertEquals("8082", thirdRow.getColumns(1));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCollectWithNon200Status() throws IOException {
|
||||
try (MockedStatic<CommonHttpClient> mockedHttpClient = Mockito.mockStatic(CommonHttpClient.class)) {
|
||||
mockedHttpClient.when(CommonHttpClient::getHttpClient).thenReturn(httpClient);
|
||||
|
||||
when(httpClient.execute(any(HttpUriRequest.class), nullable(HttpContext.class))).thenReturn(httpResponse);
|
||||
|
||||
StatusLine statusLine = new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 404, "Not Found");
|
||||
when(httpResponse.getStatusLine()).thenReturn(statusLine);
|
||||
|
||||
httpSdCollectImpl.collect(builder, metrics);
|
||||
|
||||
assertEquals(CollectRep.Code.FAIL, builder.getCode());
|
||||
assertEquals("StatusCode 404", builder.getMsg());
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@Test
|
||||
void testCollectWithException() {
|
||||
String exceptionMessage = "Connection refused";
|
||||
IOException testException = new IOException(exceptionMessage);
|
||||
|
||||
try (MockedStatic<CommonHttpClient> mockedHttpClient = Mockito.mockStatic(CommonHttpClient.class);
|
||||
MockedStatic<CommonUtil> mockedCommonUtil = Mockito.mockStatic(CommonUtil.class)) {
|
||||
|
||||
mockedHttpClient.when(CommonHttpClient::getHttpClient).thenReturn(httpClient);
|
||||
|
||||
when(httpClient.execute(any(HttpUriRequest.class), nullable(HttpContext.class))).thenThrow(testException);
|
||||
mockedCommonUtil.when(() -> CommonUtil.getMessageFromThrowable(any(Throwable.class)))
|
||||
.thenReturn(exceptionMessage);
|
||||
|
||||
httpSdCollectImpl.collect(builder, metrics);
|
||||
|
||||
assertEquals(CollectRep.Code.FAIL, builder.getCode());
|
||||
assertEquals(exceptionMessage, builder.getMsg());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCollectWithEmptyServiceList() throws IOException {
|
||||
List<ServiceDiscoveryResponseEntity> emptyList = Collections.emptyList();
|
||||
String jsonResponse = JsonUtil.toJson(emptyList);
|
||||
|
||||
try (MockedStatic<CommonHttpClient> mockedHttpClient = Mockito.mockStatic(CommonHttpClient.class)) {
|
||||
mockedHttpClient.when(CommonHttpClient::getHttpClient).thenReturn(httpClient);
|
||||
|
||||
when(httpClient.execute(any(HttpUriRequest.class), nullable(HttpContext.class))).thenReturn(httpResponse);
|
||||
|
||||
StatusLine statusLine = new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK");
|
||||
when(httpResponse.getStatusLine()).thenReturn(statusLine);
|
||||
|
||||
HttpEntity entity = new StringEntity(jsonResponse, ContentType.APPLICATION_JSON);
|
||||
when(httpResponse.getEntity()).thenReturn(entity);
|
||||
|
||||
httpSdCollectImpl.collect(builder, metrics);
|
||||
|
||||
assertEquals(0, builder.getValuesCount());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSupportProtocol() {
|
||||
assertEquals(DispatchConstants.PROTOCOL_HTTP_SD, httpSdCollectImpl.supportProtocol());
|
||||
}
|
||||
}
|
||||
+233
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
* 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.collector.collect.sd;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import org.apache.hertzbeat.collector.collect.registry.discovery.DiscoveryClient;
|
||||
import org.apache.hertzbeat.collector.collect.registry.discovery.DiscoveryClientManagement;
|
||||
import org.apache.hertzbeat.collector.collect.registry.discovery.entity.ServiceInstance;
|
||||
import org.apache.hertzbeat.collector.dispatch.DispatchConstants;
|
||||
import org.apache.hertzbeat.common.entity.job.Metrics;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.NacosSdProtocol;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.RegistryProtocol;
|
||||
import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
import org.apache.hertzbeat.common.util.CommonUtil;
|
||||
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.MockedStatic;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
/**
|
||||
* Test case for {@link NacosSdCollectImpl}
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class NacosSdCollectImplTest {
|
||||
|
||||
@InjectMocks
|
||||
private NacosSdCollectImpl nacosSdCollectImpl;
|
||||
|
||||
@Mock
|
||||
private DiscoveryClientManagement discoveryClientManagement;
|
||||
|
||||
@Mock
|
||||
private DiscoveryClient discoveryClient;
|
||||
|
||||
private Metrics metrics;
|
||||
private NacosSdProtocol nacosSdProtocol;
|
||||
private CollectRep.MetricsData.Builder builder;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
nacosSdProtocol = NacosSdProtocol.builder()
|
||||
.host("localhost")
|
||||
.port("8848")
|
||||
.username("nacos")
|
||||
.password("nacos")
|
||||
.build();
|
||||
|
||||
metrics = Metrics.builder()
|
||||
.nacos_sd(nacosSdProtocol)
|
||||
.build();
|
||||
|
||||
builder = CollectRep.MetricsData.newBuilder();
|
||||
|
||||
ReflectionTestUtils.setField(nacosSdCollectImpl, "discoveryClientManagement", discoveryClientManagement);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPreCheckWithNullMetrics() {
|
||||
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
|
||||
() -> nacosSdCollectImpl.preCheck(null));
|
||||
|
||||
assertEquals("Nacos service discovery monitoring, the config is null", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPreCheckWithNullNacosSdConfig() {
|
||||
Metrics metricsWithoutNacosSd = Metrics.builder().build();
|
||||
|
||||
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
|
||||
() -> nacosSdCollectImpl.preCheck(metricsWithoutNacosSd));
|
||||
|
||||
assertEquals("Nacos service discovery monitoring, the config is null", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPreCheckWithInvalidNacosSdConfig() {
|
||||
NacosSdProtocol invalidNacosSdProtocol = NacosSdProtocol.builder()
|
||||
.host("")
|
||||
.port("8848")
|
||||
.build();
|
||||
|
||||
Metrics metricsWithInvalidNacosSd = Metrics.builder()
|
||||
.nacos_sd(invalidNacosSdProtocol)
|
||||
.build();
|
||||
|
||||
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
|
||||
() -> nacosSdCollectImpl.preCheck(metricsWithInvalidNacosSd));
|
||||
|
||||
assertEquals("Nacos service discovery monitoring, the config is invalid", exception.getMessage());
|
||||
|
||||
NacosSdProtocol portInvalidNacosSdProtocol = NacosSdProtocol.builder()
|
||||
.host("localhost")
|
||||
.port("")
|
||||
.build();
|
||||
|
||||
Metrics portMetricsWithInvalidNacosSd = Metrics.builder()
|
||||
.nacos_sd(portInvalidNacosSdProtocol)
|
||||
.build();
|
||||
|
||||
exception = assertThrows(IllegalArgumentException.class,
|
||||
() -> nacosSdCollectImpl.preCheck(portMetricsWithInvalidNacosSd));
|
||||
|
||||
assertEquals("Nacos service discovery monitoring, the config is invalid", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPreCheckWithValidConfig() {
|
||||
assertDoesNotThrow(() -> nacosSdCollectImpl.preCheck(metrics));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCollectSuccess() throws Exception {
|
||||
List<ServiceInstance> services = new ArrayList<>();
|
||||
services.add(ServiceInstance.builder()
|
||||
.serviceId("service-1")
|
||||
.serviceName("user-service")
|
||||
.address("192.168.1.10")
|
||||
.port(8080)
|
||||
.healthStatus("UP")
|
||||
.build());
|
||||
services.add(ServiceInstance.builder()
|
||||
.serviceId("service-2")
|
||||
.serviceName("order-service")
|
||||
.address("192.168.1.11")
|
||||
.port(8081)
|
||||
.healthStatus("UP")
|
||||
.build());
|
||||
|
||||
when(discoveryClientManagement.getClient(any(RegistryProtocol.class))).thenReturn(discoveryClient);
|
||||
|
||||
when(discoveryClient.getServices()).thenReturn(services);
|
||||
|
||||
nacosSdCollectImpl.collect(builder, metrics);
|
||||
|
||||
|
||||
verify(discoveryClient, times(1)).close();
|
||||
|
||||
assertEquals(2, builder.getValuesCount());
|
||||
|
||||
CollectRep.ValueRow firstRow = builder.getValues(0);
|
||||
assertEquals("192.168.1.10", firstRow.getColumns(0));
|
||||
assertEquals("8080", firstRow.getColumns(1));
|
||||
assertEquals("user-service", firstRow.getColumns(2));
|
||||
assertEquals("UP", firstRow.getColumns(3));
|
||||
|
||||
CollectRep.ValueRow secondRow = builder.getValues(1);
|
||||
assertEquals("192.168.1.11", secondRow.getColumns(0));
|
||||
assertEquals("8081", secondRow.getColumns(1));
|
||||
assertEquals("order-service", secondRow.getColumns(2));
|
||||
assertEquals("UP", secondRow.getColumns(3));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCollectClientNull() {
|
||||
when(discoveryClientManagement.getClient(any(RegistryProtocol.class))).thenReturn(null);
|
||||
|
||||
nacosSdCollectImpl.collect(builder, metrics);
|
||||
|
||||
assertEquals(CollectRep.Code.FAIL, builder.getCode());
|
||||
assertEquals("Failed to get Nacos discovery client", builder.getMsg());
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@Test
|
||||
void testCollectWithException() {
|
||||
String exceptionMessage = "Connection refused";
|
||||
Exception testException = new RuntimeException(exceptionMessage);
|
||||
|
||||
when(discoveryClientManagement.getClient(any(RegistryProtocol.class))).thenReturn(discoveryClient);
|
||||
when(discoveryClient.getServices()).thenThrow(testException);
|
||||
|
||||
try (MockedStatic<CommonUtil> mockedCommonUtil = Mockito.mockStatic(CommonUtil.class)) {
|
||||
mockedCommonUtil.when(() -> CommonUtil.getMessageFromThrowable(any(Throwable.class)))
|
||||
.thenReturn(exceptionMessage);
|
||||
|
||||
nacosSdCollectImpl.collect(builder, metrics);
|
||||
|
||||
assertEquals(CollectRep.Code.FAIL, builder.getCode());
|
||||
assertEquals(exceptionMessage, builder.getMsg());
|
||||
|
||||
verify(discoveryClient, times(1)).close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCollectWithEmptyServiceList() throws Exception {
|
||||
when(discoveryClientManagement.getClient(any(RegistryProtocol.class))).thenReturn(discoveryClient);
|
||||
when(discoveryClient.getServices()).thenReturn(Collections.emptyList());
|
||||
|
||||
nacosSdCollectImpl.collect(builder, metrics);
|
||||
|
||||
assertEquals(0, builder.getValuesCount());
|
||||
|
||||
verify(discoveryClient, times(1)).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSupportProtocol() {
|
||||
assertEquals(DispatchConstants.PROTOCOL_NACOS_SD, nacosSdCollectImpl.supportProtocol());
|
||||
}
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* 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.collector.collect.sd;
|
||||
|
||||
import org.apache.hertzbeat.common.entity.job.Metrics;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.ZookeeperSdProtocol;
|
||||
import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
import org.apache.zookeeper.ZooKeeper;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.MockedConstruction;
|
||||
import org.mockito.Mockito;
|
||||
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.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class ZookeeperSdCollectImplTest {
|
||||
|
||||
private ZookeeperSdCollectImpl zookeeperSdCollect;
|
||||
|
||||
private Metrics metrics;
|
||||
private ZookeeperSdProtocol protocol;
|
||||
private CollectRep.MetricsData.Builder builder;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
zookeeperSdCollect = new ZookeeperSdCollectImpl();
|
||||
|
||||
protocol = ZookeeperSdProtocol.builder()
|
||||
.url("localhost:2181")
|
||||
.pathPrefix("/services")
|
||||
.build();
|
||||
|
||||
metrics = Metrics.builder()
|
||||
.zookeeper_sd(protocol)
|
||||
.build();
|
||||
|
||||
builder = CollectRep.MetricsData.newBuilder();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPreCheckWithNullConfig() {
|
||||
Metrics invalid = Metrics.builder().build();
|
||||
IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
|
||||
() -> zookeeperSdCollect.preCheck(invalid));
|
||||
assertEquals("Zookeeper SD configuration cannot be null", e.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPreCheckWithEmptyUrl() {
|
||||
ZookeeperSdProtocol badProtocol = ZookeeperSdProtocol.builder().url("").pathPrefix("/path").build();
|
||||
Metrics m = Metrics.builder().zookeeper_sd(badProtocol).build();
|
||||
IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
|
||||
() -> zookeeperSdCollect.preCheck(m));
|
||||
assertEquals("Zookeeper URL cannot be null or empty", e.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPreCheckWithEmptyPath() {
|
||||
ZookeeperSdProtocol badProtocol = ZookeeperSdProtocol.builder().url("host").pathPrefix("").build();
|
||||
Metrics m = Metrics.builder().zookeeper_sd(badProtocol).build();
|
||||
IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
|
||||
() -> zookeeperSdCollect.preCheck(m));
|
||||
assertEquals("Zookeeper path prefix cannot be null or empty", e.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPreCheckValid() {
|
||||
assertDoesNotThrow(() -> zookeeperSdCollect.preCheck(metrics));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSupportProtocol() {
|
||||
assertEquals("zookeeper_sd", zookeeperSdCollect.supportProtocol());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCollectSuccess() throws Exception {
|
||||
try (MockedConstruction<ZooKeeper> mocked = Mockito.mockConstruction(ZooKeeper.class,
|
||||
(mock, context) -> Mockito.when(mock.getChildren("/services", false)).thenReturn(List.of("host1:8080", "host2:9090")))) {
|
||||
|
||||
zookeeperSdCollect.collect(builder, metrics);
|
||||
|
||||
assertEquals(2, builder.getValuesCount());
|
||||
assertEquals("host1", builder.getValues(0).getColumns(0));
|
||||
assertEquals("8080", builder.getValues(0).getColumns(1));
|
||||
assertEquals("host2", builder.getValues(1).getColumns(0));
|
||||
assertEquals("9090", builder.getValues(1).getColumns(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -28,7 +28,7 @@ import java.util.concurrent.ScheduledExecutorService;
|
||||
import org.apache.hertzbeat.collector.dispatch.CollectorInfoProperties;
|
||||
import org.apache.hertzbeat.collector.dispatch.DispatchProperties;
|
||||
import org.apache.hertzbeat.collector.dispatch.entrance.internal.CollectJobService;
|
||||
import org.apache.hertzbeat.collector.dispatch.timer.TimerDispatch;
|
||||
import org.apache.hertzbeat.collector.timer.TimerDispatch;
|
||||
import org.apache.hertzbeat.common.entity.message.ClusterMsg;
|
||||
import org.apache.hertzbeat.common.support.CommonThreadPool;
|
||||
import org.apache.hertzbeat.remoting.RemotingClient;
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ import java.util.concurrent.CountDownLatch;
|
||||
import org.apache.hertzbeat.collector.dispatch.DispatchProperties;
|
||||
import org.apache.hertzbeat.collector.dispatch.WorkerPool;
|
||||
import org.apache.hertzbeat.collector.dispatch.entrance.CollectServer;
|
||||
import org.apache.hertzbeat.collector.dispatch.timer.TimerDispatch;
|
||||
import org.apache.hertzbeat.collector.timer.TimerDispatch;
|
||||
import org.apache.hertzbeat.common.entity.job.Job;
|
||||
import org.apache.hertzbeat.common.entity.message.ClusterMsg;
|
||||
import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
|
||||
+3
-3
@@ -23,9 +23,9 @@ import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.collector.dispatch.entrance.internal.CollectJobService;
|
||||
import org.apache.hertzbeat.collector.dispatch.timer.Timeout;
|
||||
import org.apache.hertzbeat.collector.dispatch.timer.TimerDispatch;
|
||||
import org.apache.hertzbeat.collector.dispatch.timer.WheelTimerTask;
|
||||
import org.apache.hertzbeat.common.timer.Timeout;
|
||||
import org.apache.hertzbeat.collector.timer.TimerDispatch;
|
||||
import org.apache.hertzbeat.collector.timer.WheelTimerTask;
|
||||
import org.apache.hertzbeat.collector.dispatch.unit.UnitConvert;
|
||||
import org.apache.hertzbeat.collector.util.CollectUtil;
|
||||
import org.apache.hertzbeat.common.entity.job.Configmap;
|
||||
|
||||
+31
-6
@@ -23,8 +23,8 @@ import org.apache.commons.jexl3.JexlExpression;
|
||||
import org.apache.hertzbeat.collector.collect.AbstractCollect;
|
||||
import org.apache.hertzbeat.collector.collect.prometheus.PrometheusAutoCollectImpl;
|
||||
import org.apache.hertzbeat.collector.collect.strategy.CollectStrategyFactory;
|
||||
import org.apache.hertzbeat.collector.dispatch.timer.Timeout;
|
||||
import org.apache.hertzbeat.collector.dispatch.timer.WheelTimerTask;
|
||||
import org.apache.hertzbeat.common.timer.Timeout;
|
||||
import org.apache.hertzbeat.collector.timer.WheelTimerTask;
|
||||
import org.apache.hertzbeat.collector.dispatch.unit.UnitConvert;
|
||||
import org.apache.hertzbeat.collector.util.CollectUtil;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
@@ -115,6 +115,10 @@ public class MetricsCollect implements Runnable, Comparable<MetricsCollect> {
|
||||
* Whether it is a service discovery job, true is yes, false is no
|
||||
*/
|
||||
protected boolean isSd;
|
||||
/**
|
||||
* Whether to use the Prometheus proxy
|
||||
*/
|
||||
protected boolean prometheusProxyMode;
|
||||
|
||||
protected List<UnitConvert> unitConvertList;
|
||||
|
||||
@@ -137,6 +141,7 @@ public class MetricsCollect implements Runnable, Comparable<MetricsCollect> {
|
||||
this.collectDataDispatch = collectDataDispatch;
|
||||
this.isCyclic = job.isCyclic();
|
||||
this.isSd = job.isSd();
|
||||
this.prometheusProxyMode = job.isPrometheusProxyMode();
|
||||
this.unitConvertList = unitConvertList;
|
||||
// Temporary one-time tasks are executed with high priority
|
||||
if (isCyclic) {
|
||||
@@ -153,11 +158,31 @@ public class MetricsCollect implements Runnable, Comparable<MetricsCollect> {
|
||||
CollectRep.MetricsData.Builder response = CollectRep.MetricsData.newBuilder();
|
||||
response.setApp(app).setId(id).setTenantId(tenantId)
|
||||
.setLabels(labels).setAnnotations(annotations).addMetadataAll(metadata);
|
||||
// for prometheus auto
|
||||
// for prometheus auto or proxy mode
|
||||
if (DispatchConstants.PROTOCOL_PROMETHEUS.equalsIgnoreCase(metrics.getProtocol())) {
|
||||
List<CollectRep.MetricsData> metricsData = PrometheusAutoCollectImpl
|
||||
.getInstance().collect(response, metrics);
|
||||
validateResponse(metricsData.stream().findFirst().orElse(null));
|
||||
List<CollectRep.MetricsData> metricsData;
|
||||
|
||||
// TODO: Refactor Prometheus metrics collection logic.
|
||||
// The current implementation for proxy mode and auto mode needs review and potential simplification.
|
||||
// Consider a more unified approach or clarify the conditions for each mode.
|
||||
/*
|
||||
// TODO USE PROXY MODE
|
||||
if (prometheusProxyMode) {
|
||||
List<CollectRep.MetricsData> proxyData = PrometheusProxyCollectImpl.getInstance().collect(response, metrics);
|
||||
List<CollectRep.MetricsData> autoData = PrometheusAutoCollectImpl.getInstance().collect(response, metrics);
|
||||
metricsData = new LinkedList<>();
|
||||
if (proxyData != null) {
|
||||
metricsData.addAll(proxyData);
|
||||
}
|
||||
if (autoData != null) {
|
||||
metricsData.addAll(autoData);
|
||||
}
|
||||
} else {
|
||||
metricsData = PrometheusAutoCollectImpl.getInstance().collect(response, metrics);
|
||||
}
|
||||
*/
|
||||
metricsData = PrometheusAutoCollectImpl.getInstance().collect(response, metrics);
|
||||
validateResponse(metricsData == null ? null : metricsData.stream().findFirst().orElse(null));
|
||||
collectDataDispatch.dispatchCollectData(timeout, metrics, metricsData);
|
||||
return;
|
||||
}
|
||||
|
||||
+1
@@ -29,6 +29,7 @@ org.apache.hertzbeat.collector.collect.mqtt.MqttCollectImpl
|
||||
org.apache.hertzbeat.collector.collect.ipmi2.IpmiCollectImpl
|
||||
org.apache.hertzbeat.collector.collect.kafka.KafkaCollectImpl
|
||||
org.apache.hertzbeat.collector.collect.sd.HttpSdCollectImpl
|
||||
org.apache.hertzbeat.collector.collect.sd.NacosSdCollectImpl
|
||||
org.apache.hertzbeat.collector.collect.sd.DnsSdCollectImpl
|
||||
org.apache.hertzbeat.collector.collect.sd.EurekaSdCollectImpl
|
||||
org.apache.hertzbeat.collector.collect.sd.ConsulSdCollectImpl
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ spring:
|
||||
|
||||
collector:
|
||||
info:
|
||||
version: ${COLLECTOR_VERSION:1.7.1}
|
||||
version: ${COLLECTOR_VERSION:1.7.2}
|
||||
ip: ${COLLECTOR_IP:}
|
||||
dispatch:
|
||||
entrance:
|
||||
|
||||
+1
-1
@@ -47,6 +47,6 @@ public interface CollectorConstants extends NetworkConstants {
|
||||
|
||||
String RESPONSE_TIME = "responseTime";
|
||||
|
||||
String STATUS_CODE = "StatusCode";
|
||||
String STATUS_CODE = "statusCode";
|
||||
|
||||
}
|
||||
+1
-1
@@ -17,7 +17,7 @@
|
||||
|
||||
package org.apache.hertzbeat.collector.dispatch;
|
||||
|
||||
import org.apache.hertzbeat.collector.dispatch.timer.Timeout;
|
||||
import org.apache.hertzbeat.common.timer.Timeout;
|
||||
import org.apache.hertzbeat.common.entity.job.Metrics;
|
||||
import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
|
||||
|
||||
+4
@@ -127,6 +127,10 @@ public interface DispatchConstants {
|
||||
* protocol http sd
|
||||
*/
|
||||
String PROTOCOL_HTTP_SD = "http_sd";
|
||||
/**
|
||||
* protocol nacos sd
|
||||
*/
|
||||
String PROTOCOL_NACOS_SD = "nacos_sd";
|
||||
/**
|
||||
* protocol dns sd
|
||||
*/
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@
|
||||
|
||||
package org.apache.hertzbeat.collector.dispatch;
|
||||
|
||||
import org.apache.hertzbeat.collector.dispatch.timer.Timeout;
|
||||
import org.apache.hertzbeat.common.timer.Timeout;
|
||||
|
||||
/**
|
||||
* Metrics collection task scheduler interface
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ import org.apache.hertzbeat.collector.dispatch.entrance.processor.GoCloseProcess
|
||||
import org.apache.hertzbeat.collector.dispatch.entrance.processor.GoOfflineProcessor;
|
||||
import org.apache.hertzbeat.collector.dispatch.entrance.processor.GoOnlineProcessor;
|
||||
import org.apache.hertzbeat.collector.dispatch.entrance.processor.HeartbeatProcessor;
|
||||
import org.apache.hertzbeat.collector.dispatch.timer.TimerDispatch;
|
||||
import org.apache.hertzbeat.collector.timer.TimerDispatch;
|
||||
import org.apache.hertzbeat.common.entity.dto.CollectorInfo;
|
||||
import org.apache.hertzbeat.common.entity.message.ClusterMsg;
|
||||
import org.apache.hertzbeat.common.support.CommonThreadPool;
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.collector.dispatch.DispatchProperties;
|
||||
import org.apache.hertzbeat.collector.dispatch.WorkerPool;
|
||||
import org.apache.hertzbeat.collector.dispatch.entrance.CollectServer;
|
||||
import org.apache.hertzbeat.collector.dispatch.timer.TimerDispatch;
|
||||
import org.apache.hertzbeat.collector.timer.TimerDispatch;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.job.Job;
|
||||
import org.apache.hertzbeat.common.entity.message.ClusterMsg;
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ package org.apache.hertzbeat.collector.dispatch.entrance.processor;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.collector.dispatch.entrance.CollectServer;
|
||||
import org.apache.hertzbeat.collector.dispatch.timer.TimerDispatch;
|
||||
import org.apache.hertzbeat.collector.timer.TimerDispatch;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.message.ClusterMsg;
|
||||
import org.apache.hertzbeat.common.support.SpringContextHolder;
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ package org.apache.hertzbeat.collector.dispatch.entrance.processor;
|
||||
import com.google.protobuf.ByteString;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.collector.dispatch.timer.TimerDispatch;
|
||||
import org.apache.hertzbeat.collector.timer.TimerDispatch;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.message.ClusterMsg;
|
||||
import org.apache.hertzbeat.common.support.SpringContextHolder;
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ package org.apache.hertzbeat.collector.dispatch.entrance.processor;
|
||||
import com.google.protobuf.ByteString;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.collector.dispatch.timer.TimerDispatch;
|
||||
import org.apache.hertzbeat.collector.timer.TimerDispatch;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.message.ClusterMsg;
|
||||
import org.apache.hertzbeat.common.support.SpringContextHolder;
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.collector.dispatch.timer;
|
||||
package org.apache.hertzbeat.collector.timer;
|
||||
|
||||
import org.apache.hertzbeat.collector.dispatch.entrance.internal.CollectResponseEventListener;
|
||||
import org.apache.hertzbeat.common.entity.job.Job;
|
||||
+4
-1
@@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.collector.dispatch.timer;
|
||||
package org.apache.hertzbeat.collector.timer;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -28,6 +28,9 @@ import org.apache.hertzbeat.collector.dispatch.entrance.internal.CollectResponse
|
||||
import org.apache.hertzbeat.common.entity.job.Job;
|
||||
import org.apache.hertzbeat.common.entity.job.Metrics;
|
||||
import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
import org.apache.hertzbeat.common.timer.HashedWheelTimer;
|
||||
import org.apache.hertzbeat.common.timer.Timeout;
|
||||
import org.apache.hertzbeat.common.timer.Timer;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
+3
-1
@@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.collector.dispatch.timer;
|
||||
package org.apache.hertzbeat.collector.timer;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
@@ -28,6 +28,8 @@ import org.apache.hertzbeat.common.entity.job.Configmap;
|
||||
import org.apache.hertzbeat.common.entity.job.Job;
|
||||
import org.apache.hertzbeat.common.entity.job.Metrics;
|
||||
import org.apache.hertzbeat.common.support.SpringContextHolder;
|
||||
import org.apache.hertzbeat.common.timer.Timeout;
|
||||
import org.apache.hertzbeat.common.timer.TimerTask;
|
||||
import org.apache.hertzbeat.common.util.AesUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
+259
@@ -0,0 +1,259 @@
|
||||
#!/bin/sh
|
||||
# ----------------------------------------------------------------------------
|
||||
# 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.
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Apache Maven Wrapper startup batch script, version 3.3.2
|
||||
#
|
||||
# Optional ENV vars
|
||||
# -----------------
|
||||
# JAVA_HOME - location of a JDK home dir, required when download maven via java source
|
||||
# MVNW_REPOURL - repo url base for downloading maven distribution
|
||||
# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
|
||||
# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
set -euf
|
||||
[ "${MVNW_VERBOSE-}" != debug ] || set -x
|
||||
|
||||
# OS specific support.
|
||||
native_path() { printf %s\\n "$1"; }
|
||||
case "$(uname)" in
|
||||
CYGWIN* | MINGW*)
|
||||
[ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
|
||||
native_path() { cygpath --path --windows "$1"; }
|
||||
;;
|
||||
esac
|
||||
|
||||
# set JAVACMD and JAVACCMD
|
||||
set_java_home() {
|
||||
# For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
|
||||
if [ -n "${JAVA_HOME-}" ]; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ]; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
JAVACCMD="$JAVA_HOME/jre/sh/javac"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
JAVACCMD="$JAVA_HOME/bin/javac"
|
||||
|
||||
if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
|
||||
echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
|
||||
echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
else
|
||||
JAVACMD="$(
|
||||
'set' +e
|
||||
'unset' -f command 2>/dev/null
|
||||
'command' -v java
|
||||
)" || :
|
||||
JAVACCMD="$(
|
||||
'set' +e
|
||||
'unset' -f command 2>/dev/null
|
||||
'command' -v javac
|
||||
)" || :
|
||||
|
||||
if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
|
||||
echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# hash string like Java String::hashCode
|
||||
hash_string() {
|
||||
str="${1:-}" h=0
|
||||
while [ -n "$str" ]; do
|
||||
char="${str%"${str#?}"}"
|
||||
h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
|
||||
str="${str#?}"
|
||||
done
|
||||
printf %x\\n $h
|
||||
}
|
||||
|
||||
verbose() { :; }
|
||||
[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
|
||||
|
||||
die() {
|
||||
printf %s\\n "$1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
trim() {
|
||||
# MWRAPPER-139:
|
||||
# Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
|
||||
# Needed for removing poorly interpreted newline sequences when running in more
|
||||
# exotic environments such as mingw bash on Windows.
|
||||
printf "%s" "${1}" | tr -d '[:space:]'
|
||||
}
|
||||
|
||||
# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
|
||||
while IFS="=" read -r key value; do
|
||||
case "${key-}" in
|
||||
distributionUrl) distributionUrl=$(trim "${value-}") ;;
|
||||
distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
|
||||
esac
|
||||
done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties"
|
||||
[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties"
|
||||
|
||||
case "${distributionUrl##*/}" in
|
||||
maven-mvnd-*bin.*)
|
||||
MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
|
||||
case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
|
||||
*AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
|
||||
:Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
|
||||
:Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
|
||||
:Linux*x86_64*) distributionPlatform=linux-amd64 ;;
|
||||
*)
|
||||
echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
|
||||
distributionPlatform=linux-amd64
|
||||
;;
|
||||
esac
|
||||
distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
|
||||
;;
|
||||
maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
|
||||
*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
|
||||
esac
|
||||
|
||||
# apply MVNW_REPOURL and calculate MAVEN_HOME
|
||||
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
|
||||
[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
|
||||
distributionUrlName="${distributionUrl##*/}"
|
||||
distributionUrlNameMain="${distributionUrlName%.*}"
|
||||
distributionUrlNameMain="${distributionUrlNameMain%-bin}"
|
||||
MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
|
||||
MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
|
||||
|
||||
exec_maven() {
|
||||
unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
|
||||
exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
|
||||
}
|
||||
|
||||
if [ -d "$MAVEN_HOME" ]; then
|
||||
verbose "found existing MAVEN_HOME at $MAVEN_HOME"
|
||||
exec_maven "$@"
|
||||
fi
|
||||
|
||||
case "${distributionUrl-}" in
|
||||
*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
|
||||
*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
|
||||
esac
|
||||
|
||||
# prepare tmp dir
|
||||
if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
|
||||
clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
|
||||
trap clean HUP INT TERM EXIT
|
||||
else
|
||||
die "cannot create temp dir"
|
||||
fi
|
||||
|
||||
mkdir -p -- "${MAVEN_HOME%/*}"
|
||||
|
||||
# Download and Install Apache Maven
|
||||
verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
|
||||
verbose "Downloading from: $distributionUrl"
|
||||
verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
|
||||
|
||||
# select .zip or .tar.gz
|
||||
if ! command -v unzip >/dev/null; then
|
||||
distributionUrl="${distributionUrl%.zip}.tar.gz"
|
||||
distributionUrlName="${distributionUrl##*/}"
|
||||
fi
|
||||
|
||||
# verbose opt
|
||||
__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
|
||||
[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
|
||||
|
||||
# normalize http auth
|
||||
case "${MVNW_PASSWORD:+has-password}" in
|
||||
'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
|
||||
has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
|
||||
esac
|
||||
|
||||
if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
|
||||
verbose "Found wget ... using wget"
|
||||
wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
|
||||
elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
|
||||
verbose "Found curl ... using curl"
|
||||
curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
|
||||
elif set_java_home; then
|
||||
verbose "Falling back to use Java to download"
|
||||
javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
|
||||
targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
|
||||
cat >"$javaSource" <<-END
|
||||
public class Downloader extends java.net.Authenticator
|
||||
{
|
||||
protected java.net.PasswordAuthentication getPasswordAuthentication()
|
||||
{
|
||||
return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
|
||||
}
|
||||
public static void main( String[] args ) throws Exception
|
||||
{
|
||||
setDefault( new Downloader() );
|
||||
java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
|
||||
}
|
||||
}
|
||||
END
|
||||
# For Cygwin/MinGW, switch paths to Windows format before running javac and java
|
||||
verbose " - Compiling Downloader.java ..."
|
||||
"$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
|
||||
verbose " - Running Downloader.java ..."
|
||||
"$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
|
||||
fi
|
||||
|
||||
# If specified, validate the SHA-256 sum of the Maven distribution zip file
|
||||
if [ -n "${distributionSha256Sum-}" ]; then
|
||||
distributionSha256Result=false
|
||||
if [ "$MVN_CMD" = mvnd.sh ]; then
|
||||
echo "Checksum validation is not supported for maven-mvnd." >&2
|
||||
echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
|
||||
exit 1
|
||||
elif command -v sha256sum >/dev/null; then
|
||||
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then
|
||||
distributionSha256Result=true
|
||||
fi
|
||||
elif command -v shasum >/dev/null; then
|
||||
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
|
||||
distributionSha256Result=true
|
||||
fi
|
||||
else
|
||||
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
|
||||
echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ $distributionSha256Result = false ]; then
|
||||
echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
|
||||
echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# unzip and move
|
||||
if command -v unzip >/dev/null; then
|
||||
unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
|
||||
else
|
||||
tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
|
||||
fi
|
||||
printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url"
|
||||
mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
|
||||
|
||||
clean || :
|
||||
exec_maven "$@"
|
||||
Vendored
+149
@@ -0,0 +1,149 @@
|
||||
<# : batch portion
|
||||
@REM ----------------------------------------------------------------------------
|
||||
@REM Licensed to the Apache Software Foundation (ASF) under one
|
||||
@REM or more contributor license agreements. See the NOTICE file
|
||||
@REM distributed with this work for additional information
|
||||
@REM regarding copyright ownership. The ASF licenses this file
|
||||
@REM to you under the Apache License, Version 2.0 (the
|
||||
@REM "License"); you may not use this file except in compliance
|
||||
@REM with the License. You may obtain a copy of the License at
|
||||
@REM
|
||||
@REM http://www.apache.org/licenses/LICENSE-2.0
|
||||
@REM
|
||||
@REM Unless required by applicable law or agreed to in writing,
|
||||
@REM software distributed under the License is distributed on an
|
||||
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
@REM KIND, either express or implied. See the License for the
|
||||
@REM specific language governing permissions and limitations
|
||||
@REM under the License.
|
||||
@REM ----------------------------------------------------------------------------
|
||||
|
||||
@REM ----------------------------------------------------------------------------
|
||||
@REM Apache Maven Wrapper startup batch script, version 3.3.2
|
||||
@REM
|
||||
@REM Optional ENV vars
|
||||
@REM MVNW_REPOURL - repo url base for downloading maven distribution
|
||||
@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
|
||||
@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
|
||||
@REM ----------------------------------------------------------------------------
|
||||
|
||||
@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
|
||||
@SET __MVNW_CMD__=
|
||||
@SET __MVNW_ERROR__=
|
||||
@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
|
||||
@SET PSModulePath=
|
||||
@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
|
||||
IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
|
||||
)
|
||||
@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
|
||||
@SET __MVNW_PSMODULEP_SAVE=
|
||||
@SET __MVNW_ARG0_NAME__=
|
||||
@SET MVNW_USERNAME=
|
||||
@SET MVNW_PASSWORD=
|
||||
@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*)
|
||||
@echo Cannot start maven from wrapper >&2 && exit /b 1
|
||||
@GOTO :EOF
|
||||
: end batch / begin powershell #>
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
if ($env:MVNW_VERBOSE -eq "true") {
|
||||
$VerbosePreference = "Continue"
|
||||
}
|
||||
|
||||
# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
|
||||
$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
|
||||
if (!$distributionUrl) {
|
||||
Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
|
||||
}
|
||||
|
||||
switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
|
||||
"maven-mvnd-*" {
|
||||
$USE_MVND = $true
|
||||
$distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
|
||||
$MVN_CMD = "mvnd.cmd"
|
||||
break
|
||||
}
|
||||
default {
|
||||
$USE_MVND = $false
|
||||
$MVN_CMD = $script -replace '^mvnw','mvn'
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
# apply MVNW_REPOURL and calculate MAVEN_HOME
|
||||
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
|
||||
if ($env:MVNW_REPOURL) {
|
||||
$MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" }
|
||||
$distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')"
|
||||
}
|
||||
$distributionUrlName = $distributionUrl -replace '^.*/',''
|
||||
$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
|
||||
$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain"
|
||||
if ($env:MAVEN_USER_HOME) {
|
||||
$MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain"
|
||||
}
|
||||
$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
|
||||
$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
|
||||
|
||||
if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
|
||||
Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
|
||||
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
|
||||
exit $?
|
||||
}
|
||||
|
||||
if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
|
||||
Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
|
||||
}
|
||||
|
||||
# prepare tmp dir
|
||||
$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
|
||||
$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
|
||||
$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
|
||||
trap {
|
||||
if ($TMP_DOWNLOAD_DIR.Exists) {
|
||||
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
|
||||
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
|
||||
}
|
||||
}
|
||||
|
||||
New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
|
||||
|
||||
# Download and Install Apache Maven
|
||||
Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
|
||||
Write-Verbose "Downloading from: $distributionUrl"
|
||||
Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
|
||||
|
||||
$webclient = New-Object System.Net.WebClient
|
||||
if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
|
||||
$webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
|
||||
}
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||
$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
|
||||
|
||||
# If specified, validate the SHA-256 sum of the Maven distribution zip file
|
||||
$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
|
||||
if ($distributionSha256Sum) {
|
||||
if ($USE_MVND) {
|
||||
Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
|
||||
}
|
||||
Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
|
||||
if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
|
||||
Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
|
||||
}
|
||||
}
|
||||
|
||||
# unzip and move
|
||||
Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
|
||||
Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null
|
||||
try {
|
||||
Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
|
||||
} catch {
|
||||
if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
|
||||
Write-Error "fail to move MAVEN_HOME"
|
||||
}
|
||||
} finally {
|
||||
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
|
||||
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
|
||||
}
|
||||
|
||||
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
|
||||
@@ -142,5 +142,58 @@ public interface AiConstants {
|
||||
float TEMPERATURE = 0.7f;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Ollama constants
|
||||
*/
|
||||
interface OllamaConstants {
|
||||
/**
|
||||
* request role param
|
||||
*/
|
||||
String REQUEST_ROLE = "user";
|
||||
|
||||
/**
|
||||
* The model outputs the maximum tokens, with a maximum output of 8192 and a default value of 3072
|
||||
*/
|
||||
Integer MAX_TOKENS = 3072;
|
||||
|
||||
/**
|
||||
* The sampling temperature, which controls the randomness of the output, must be positive
|
||||
* The value ranges from 0.0 to 1.0, and cannot be equal to 0. The default value is 0.95. The larger the value,
|
||||
* the more random and creative the output will be. The smaller the value, the more stable or certain the output will be
|
||||
* You are advised to adjust top_p or temperature parameters based on application scenarios, but do not adjust the two parameters at the same time
|
||||
*/
|
||||
float TEMPERATURE = 0.7f;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* OpenRouter constants
|
||||
*/
|
||||
interface OpenRouterConstants {
|
||||
/**
|
||||
* OpenRouter Ai URL
|
||||
*/
|
||||
String URL = "https://openrouter.ai/api/v1/chat/completions";
|
||||
|
||||
/**
|
||||
* request role param
|
||||
*/
|
||||
String REQUEST_ROLE = "user";
|
||||
|
||||
/**
|
||||
* The model outputs the maximum tokens, with a maximum output of 8192 and a default value of 3072
|
||||
*/
|
||||
Integer MAX_TOKENS = 3072;
|
||||
|
||||
/**
|
||||
* The sampling temperature, which controls the randomness of the output, must be positive
|
||||
* The value ranges from 0.0 to 1.0, and cannot be equal to 0. The default value is 0.95. The larger the value,
|
||||
* the more random and creative the output will be. The smaller the value, the more stable or certain the output will be
|
||||
* You are advised to adjust top_p or temperature parameters based on application scenarios, but do not adjust the two parameters at the same time
|
||||
*/
|
||||
float TEMPERATURE = 0.7f;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+10
-1
@@ -42,8 +42,17 @@ public enum AiTypeEnum {
|
||||
/**
|
||||
* Moonshot AI
|
||||
*/
|
||||
kimiAi;
|
||||
kimiAi,
|
||||
|
||||
/**
|
||||
* Ollama AI
|
||||
*/
|
||||
ollama,
|
||||
|
||||
/**
|
||||
* OpenRouter
|
||||
*/
|
||||
openRouter;
|
||||
|
||||
/**
|
||||
* get type
|
||||
|
||||
+2
-2
@@ -81,12 +81,12 @@ public class GroupAlert {
|
||||
|
||||
@Schema(title = "Common Annotations", example = "{\"summary\": \"High CPU usage detected\", \"description\": \"CPU usage is back to normal for server1\"}")
|
||||
@Convert(converter = JsonMapAttributeConverter.class)
|
||||
@Column(length = 4096)
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private Map<String, String> commonAnnotations;
|
||||
|
||||
@Schema(title = "Alert Fingerprints", example = "[\"dxsdfdsf\"]")
|
||||
@Convert(converter = JsonStringListAttributeConverter.class)
|
||||
@Column(length = 8192)
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private List<String> alertFingerprints;
|
||||
|
||||
@Schema(title = "The creator of this record", example = "tom")
|
||||
|
||||
@@ -138,6 +138,11 @@ public class Job {
|
||||
*/
|
||||
private boolean isSd = false;
|
||||
|
||||
/**
|
||||
* Whether to use the Prometheus proxy
|
||||
*/
|
||||
private boolean prometheusProxyMode = false;
|
||||
|
||||
/**
|
||||
* the collect data response metrics as env configmap for other collect use. ^o^xxx^o^
|
||||
*/
|
||||
|
||||
@@ -36,6 +36,7 @@ import org.apache.hertzbeat.common.entity.job.protocol.HttpProtocol;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.EurekaSdProtocol;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.ConsulSdProtocol;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.ModbusProtocol;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.NacosSdProtocol;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.PlcProtocol;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.RegistryProtocol;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.IcmpProtocol;
|
||||
@@ -66,11 +67,12 @@ import org.apache.hertzbeat.common.entity.job.protocol.TelnetProtocol;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.UdpProtocol;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.WebsocketProtocol;
|
||||
import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.ZookeeperSdProtocol;
|
||||
/**
|
||||
* Details of the monitoring metrics collected
|
||||
* eg: cpu | memory | health
|
||||
*/
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@@ -278,6 +280,14 @@ public class Metrics {
|
||||
* consul sd protocol
|
||||
*/
|
||||
private ConsulSdProtocol consul_sd;
|
||||
/**
|
||||
* nacos sd protocol
|
||||
*/
|
||||
private NacosSdProtocol nacos_sd;
|
||||
/*
|
||||
* zookeeper sd protocol
|
||||
*/
|
||||
private ZookeeperSdProtocol zookeeper_sd;
|
||||
/**
|
||||
* Monitoring configuration information using the public plc protocol
|
||||
*/
|
||||
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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.entity.job.protocol;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Nacos Service Discovery Protocol
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class NacosSdProtocol implements Protocol {
|
||||
/**
|
||||
* Nacos server host
|
||||
*/
|
||||
private String host;
|
||||
/**
|
||||
* Nacos server port
|
||||
*/
|
||||
private String port;
|
||||
/**
|
||||
* Nacos namespace
|
||||
*/
|
||||
private String namespace;
|
||||
/**
|
||||
* Nacos username for authentication
|
||||
*/
|
||||
private String username;
|
||||
/**
|
||||
* Nacos password for authentication
|
||||
*/
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* Check if the essential protocol parameters are invalid
|
||||
* @return true if essential parameters are missing
|
||||
*/
|
||||
public boolean isInvalid() {
|
||||
return StringUtils.isAnyBlank(host, port);
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.common.entity.job.protocol;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
/**
|
||||
* Zookeeper service discovery protocol
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class ZookeeperSdProtocol implements Protocol{
|
||||
|
||||
private String url;
|
||||
|
||||
private String pathPrefix;
|
||||
}
|
||||
+1
-1
@@ -77,7 +77,7 @@ public class Monitor {
|
||||
@Size(max = 100)
|
||||
private String app;
|
||||
|
||||
@Schema(title = "Scrape type: static | http_sd | dns_sd", example = "static", accessMode = READ_WRITE)
|
||||
@Schema(title = "Scrape type: static | http_sd | dns_sd | zookeeper_sd", example = "static", accessMode = READ_WRITE)
|
||||
@Size(max = 100)
|
||||
private String scrape;
|
||||
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Alert expression exception
|
||||
*/
|
||||
public class AlertExpressionException extends RuntimeException {
|
||||
|
||||
public AlertExpressionException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.collector.dispatch.timer;
|
||||
package org.apache.hertzbeat.common.timer;
|
||||
|
||||
import org.apache.hertzbeat.common.util.NetworkUtil;
|
||||
import org.slf4j.Logger;
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.collector.dispatch.timer;
|
||||
package org.apache.hertzbeat.common.timer;
|
||||
|
||||
/**
|
||||
* Derived from Apache Dubbo org.apache.dubbo.common.timer.Timeout
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.collector.dispatch.timer;
|
||||
package org.apache.hertzbeat.common.timer;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.collector.dispatch.timer;
|
||||
package org.apache.hertzbeat.common.timer;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
|
||||
package org.apache.hertzbeat.common.util;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
@@ -44,6 +46,7 @@ public final class JsonUtil {
|
||||
OBJECT_MAPPER
|
||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
||||
.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
|
||||
.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY)
|
||||
.registerModule(new JavaTimeModule());
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user