mirror of
https://github.com/dromara/hertzbeat.git
synced 2026-09-17 18:19:02 +00:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
821c62ba60 | ||
|
|
fbc1c0a29a | ||
|
|
648064227f | ||
|
|
a08fdd210c |
@@ -2225,142 +2225,6 @@
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "helei1030",
|
||||
"name": "helei1030",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/11839080?v=4",
|
||||
"profile": "https://github.com/helei1030",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "pjfanning",
|
||||
"name": "PJ Fanning",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/11783444?v=4",
|
||||
"profile": "https://medium.com/@pjfanning",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "MonsterChenzhuo",
|
||||
"name": "monster",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/60029759?v=4",
|
||||
"profile": "https://github.com/MonsterChenzhuo",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "MasamiYui",
|
||||
"name": "Sherlock Yin",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/22274133?v=4",
|
||||
"profile": "https://github.com/MasamiYui",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "wanhao23",
|
||||
"name": "wanhao",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/29560961?v=4",
|
||||
"profile": "https://github.com/wanhao23",
|
||||
"contributions": [
|
||||
"code",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "jonasHanhan",
|
||||
"name": "jonasHanhan",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/130035609?v=4",
|
||||
"profile": "https://github.com/jonasHanhan",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "NikhilMurugesan",
|
||||
"name": "NikhilMurugesan",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/49281792?v=4",
|
||||
"profile": "https://github.com/NikhilMurugesan",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "myangle1120",
|
||||
"name": "myangle1120",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/19237013?v=4",
|
||||
"profile": "https://github.com/myangle1120",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "yasminvo",
|
||||
"name": "yasminvo",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/107528848?v=4",
|
||||
"profile": "https://github.com/yasminvo",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "notbugggg",
|
||||
"name": "不关银渐层的事哦",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/147966331?v=4",
|
||||
"profile": "https://github.com/notbugggg",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "yyahang",
|
||||
"name": "yyahang",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/90464876?v=4",
|
||||
"profile": "https://github.com/yyahang",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "JuJinPark",
|
||||
"name": "jujin",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/44892459?v=4",
|
||||
"profile": "https://github.com/JuJinPark",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "LL-LIN",
|
||||
"name": "LL-LIN",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/43002118?v=4",
|
||||
"profile": "https://github.com/LL-LIN",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "bigcyy",
|
||||
"name": "Yang Chen",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/73413979?v=4",
|
||||
"profile": "https://bigcyy.github.io/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "sarthakeash",
|
||||
"name": "Sarthak Arora",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/74091160?v=4",
|
||||
"profile": "https://github.com/sarthakeash",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
||||
@@ -17,9 +17,7 @@
|
||||
|
||||
/home export-ignore
|
||||
/hip export-ignore
|
||||
/template-marketplace export-ignore
|
||||
/.github export-ignore
|
||||
/.idea export-ignore
|
||||
/.devcontainer export-ignore
|
||||
.gitattributes export-ignore
|
||||
.gitignore export-ignore
|
||||
|
||||
@@ -73,7 +73,7 @@ jobs:
|
||||
|
||||
# upload application logs
|
||||
- name: Upload logs & API test reports
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: hz-logs-${{ github.run_id }}
|
||||
|
||||
@@ -31,8 +31,6 @@ name: "CodeQL"
|
||||
on:
|
||||
schedule:
|
||||
- cron: '21 13 * * 4'
|
||||
push:
|
||||
branches: [ action* ]
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
|
||||
@@ -19,7 +19,7 @@ name: DOC CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master, dev, action* ]
|
||||
branches: [ master, dev ]
|
||||
paths:
|
||||
- 'home/**'
|
||||
pull_request:
|
||||
|
||||
@@ -17,8 +17,7 @@
|
||||
|
||||
name: 'HertzBeat Doc Pdf Builder'
|
||||
on:
|
||||
push:
|
||||
branches: [ page_build* ]
|
||||
page_build
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -40,7 +39,7 @@ jobs:
|
||||
run: npx docusaurus-prince-pdf -u https://hertzbeat.apache.org/docs --output docs-en.pdf
|
||||
|
||||
- name: Upload results
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: docs-cn-pdf
|
||||
path: docs-cn.pdf
|
||||
@@ -48,7 +47,7 @@ jobs:
|
||||
retention-days: 1
|
||||
|
||||
- name: Upload results
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: docs-en-pdf
|
||||
path: docs-en.pdf
|
||||
|
||||
@@ -22,7 +22,7 @@ name: Frontend CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master, dev, action* ]
|
||||
branches: [ master, dev ]
|
||||
paths:
|
||||
- 'web-app/**'
|
||||
pull_request:
|
||||
|
||||
@@ -22,7 +22,7 @@ name: License Checker
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master, dev, action* ]
|
||||
branches: [ master, dev ]
|
||||
pull_request:
|
||||
branches: [ master, dev ]
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
run: |
|
||||
mvn clean install
|
||||
mvn clean package -Prelease -DskipTests
|
||||
cd hertzbeat-collector
|
||||
cd collector
|
||||
mvn clean package -Pcluster -DskipTests
|
||||
|
||||
- uses: docker/setup-qemu-action@v3
|
||||
|
||||
@@ -13,7 +13,6 @@ mvnw.bat
|
||||
package-lock.json
|
||||
*.zip
|
||||
jdk/
|
||||
data/
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
@@ -55,5 +54,3 @@ node_modules
|
||||
application-dev.yml
|
||||
application-mysql.yml
|
||||
application-pg.yml
|
||||
/package.json
|
||||
/yarn.lock
|
||||
|
||||
Vendored
-7
@@ -1,7 +0,0 @@
|
||||
FROM gitpod/workspace-full
|
||||
|
||||
USER gitpod
|
||||
|
||||
RUN bash -c ". /home/gitpod/.sdkman/bin/sdkman-init.sh && \
|
||||
sdk install java 17.0.3-ms && \
|
||||
sdk default java 17.0.3-ms"
|
||||
+2
-3
@@ -1,6 +1,3 @@
|
||||
image:
|
||||
file: .gitpod.Dockerfile
|
||||
|
||||
tasks:
|
||||
|
||||
- name: Run backend
|
||||
@@ -18,6 +15,8 @@ tasks:
|
||||
|
||||
- name: Setup backend
|
||||
init: |
|
||||
sdk install java 17.0.11.fx-zulu < /dev/null
|
||||
sdk default java 17.0.11.fx-zulu < /dev/null
|
||||
mvn clean install -DskipTests
|
||||
command: |
|
||||
gp sync-done setup-backend
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Apache HertzBeat (incubating)
|
||||
Copyright 2024-2025 The Apache Software Foundation
|
||||
Copyright 2024 The Apache Software Foundation
|
||||
|
||||
This product includes software developed at
|
||||
The Apache Software Foundation (http://www.apache.org/).
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
[](https://discord.gg/Fb6M73htGr)
|
||||
[](https://www.reddit.com/r/hertzbeat/)
|
||||
[](https://x.com/hertzbeat1024)
|
||||
[](https://twitter.com/hertzbeat1024)
|
||||
[](https://www.bestpractices.dev/projects/8139)
|
||||
[](https://app.codecov.io/gh/apache/hertzbeat)
|
||||
[](https://hub.docker.com/r/apache/hertzbeat)
|
||||
@@ -496,27 +496,6 @@ Thanks to these wonderful people, welcome to join us:
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Suvrat1629"><img src="https://avatars.githubusercontent.com/u/140749446?v=4?s=100" width="100px;" alt="Suvrat1629"/><br /><sub><b>Suvrat1629</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Suvrat1629" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://ghyghoo8.github.io/"><img src="https://avatars.githubusercontent.com/u/363129?v=4?s=100" width="100px;" alt="ghy"/><br /><sub><b>ghy</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ghyghoo8" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/helei1030"><img src="https://avatars.githubusercontent.com/u/11839080?v=4?s=100" width="100px;" alt="helei1030"/><br /><sub><b>helei1030</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=helei1030" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://medium.com/@pjfanning"><img src="https://avatars.githubusercontent.com/u/11783444?v=4?s=100" width="100px;" alt="PJ Fanning"/><br /><sub><b>PJ Fanning</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=pjfanning" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/MonsterChenzhuo"><img src="https://avatars.githubusercontent.com/u/60029759?v=4?s=100" width="100px;" alt="monster"/><br /><sub><b>monster</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=MonsterChenzhuo" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/MasamiYui"><img src="https://avatars.githubusercontent.com/u/22274133?v=4?s=100" width="100px;" alt="Sherlock Yin"/><br /><sub><b>Sherlock Yin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=MasamiYui" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/wanhao23"><img src="https://avatars.githubusercontent.com/u/29560961?v=4?s=100" width="100px;" alt="wanhao"/><br /><sub><b>wanhao</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=wanhao23" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=wanhao23" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jonasHanhan"><img src="https://avatars.githubusercontent.com/u/130035609?v=4?s=100" width="100px;" alt="jonasHanhan"/><br /><sub><b>jonasHanhan</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=jonasHanhan" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/NikhilMurugesan"><img src="https://avatars.githubusercontent.com/u/49281792?v=4?s=100" width="100px;" alt="NikhilMurugesan"/><br /><sub><b>NikhilMurugesan</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=NikhilMurugesan" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/myangle1120"><img src="https://avatars.githubusercontent.com/u/19237013?v=4?s=100" width="100px;" alt="myangle1120"/><br /><sub><b>myangle1120</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=myangle1120" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yasminvo"><img src="https://avatars.githubusercontent.com/u/107528848?v=4?s=100" width="100px;" alt="yasminvo"/><br /><sub><b>yasminvo</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yasminvo" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/notbugggg"><img src="https://avatars.githubusercontent.com/u/147966331?v=4?s=100" width="100px;" alt="不关银渐层的事哦"/><br /><sub><b>不关银渐层的事哦</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=notbugggg" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yyahang"><img src="https://avatars.githubusercontent.com/u/90464876?v=4?s=100" width="100px;" alt="yyahang"/><br /><sub><b>yyahang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yyahang" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/JuJinPark"><img src="https://avatars.githubusercontent.com/u/44892459?v=4?s=100" width="100px;" alt="jujin"/><br /><sub><b>jujin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=JuJinPark" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/LL-LIN"><img src="https://avatars.githubusercontent.com/u/43002118?v=4?s=100" width="100px;" alt="LL-LIN"/><br /><sub><b>LL-LIN</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=LL-LIN" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://bigcyy.github.io/"><img src="https://avatars.githubusercontent.com/u/73413979?v=4?s=100" width="100px;" alt="Yang Chen"/><br /><sub><b>Yang Chen</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=bigcyy" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sarthakeash"><img src="https://avatars.githubusercontent.com/u/74091160?v=4?s=100" width="100px;" alt="Sarthak Arora"/><br /><sub><b>Sarthak Arora</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=sarthakeash" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -543,7 +522,7 @@ WeChat Public : Search ID `usthecom`.
|
||||
|
||||
[Reddit Community](https://www.reddit.com/r/hertzbeat/)
|
||||
|
||||
[Follow Us Twitter](https://x.com/hertzbeat1024)
|
||||
[Follow Us Twitter](https://twitter.com/hertzbeat1024)
|
||||
|
||||
[Subscribe YouTube](https://www.youtube.com/channel/UCri75zfWX0GHqJFPENEbLow)
|
||||
|
||||
|
||||
+2
-23
@@ -12,7 +12,7 @@
|
||||
|
||||
[](https://discord.gg/Fb6M73htGr)
|
||||
[](https://www.reddit.com/r/hertzbeat/)
|
||||
[](https://x.com/hertzbeat1024)
|
||||
[](https://twitter.com/hertzbeat1024)
|
||||
[](https://www.bestpractices.dev/projects/8139)
|
||||
[](https://app.codecov.io/gh/apache/hertzbeat)
|
||||
[](https://hub.docker.com/r/apache/hertzbeat)
|
||||
@@ -495,27 +495,6 @@ Thanks these wonderful people, welcome to join us:
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Suvrat1629"><img src="https://avatars.githubusercontent.com/u/140749446?v=4?s=100" width="100px;" alt="Suvrat1629"/><br /><sub><b>Suvrat1629</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Suvrat1629" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://ghyghoo8.github.io/"><img src="https://avatars.githubusercontent.com/u/363129?v=4?s=100" width="100px;" alt="ghy"/><br /><sub><b>ghy</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ghyghoo8" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/helei1030"><img src="https://avatars.githubusercontent.com/u/11839080?v=4?s=100" width="100px;" alt="helei1030"/><br /><sub><b>helei1030</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=helei1030" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://medium.com/@pjfanning"><img src="https://avatars.githubusercontent.com/u/11783444?v=4?s=100" width="100px;" alt="PJ Fanning"/><br /><sub><b>PJ Fanning</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=pjfanning" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/MonsterChenzhuo"><img src="https://avatars.githubusercontent.com/u/60029759?v=4?s=100" width="100px;" alt="monster"/><br /><sub><b>monster</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=MonsterChenzhuo" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/MasamiYui"><img src="https://avatars.githubusercontent.com/u/22274133?v=4?s=100" width="100px;" alt="Sherlock Yin"/><br /><sub><b>Sherlock Yin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=MasamiYui" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/wanhao23"><img src="https://avatars.githubusercontent.com/u/29560961?v=4?s=100" width="100px;" alt="wanhao"/><br /><sub><b>wanhao</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=wanhao23" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=wanhao23" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jonasHanhan"><img src="https://avatars.githubusercontent.com/u/130035609?v=4?s=100" width="100px;" alt="jonasHanhan"/><br /><sub><b>jonasHanhan</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=jonasHanhan" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/NikhilMurugesan"><img src="https://avatars.githubusercontent.com/u/49281792?v=4?s=100" width="100px;" alt="NikhilMurugesan"/><br /><sub><b>NikhilMurugesan</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=NikhilMurugesan" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/myangle1120"><img src="https://avatars.githubusercontent.com/u/19237013?v=4?s=100" width="100px;" alt="myangle1120"/><br /><sub><b>myangle1120</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=myangle1120" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yasminvo"><img src="https://avatars.githubusercontent.com/u/107528848?v=4?s=100" width="100px;" alt="yasminvo"/><br /><sub><b>yasminvo</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yasminvo" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/notbugggg"><img src="https://avatars.githubusercontent.com/u/147966331?v=4?s=100" width="100px;" alt="不关银渐层的事哦"/><br /><sub><b>不关银渐层的事哦</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=notbugggg" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yyahang"><img src="https://avatars.githubusercontent.com/u/90464876?v=4?s=100" width="100px;" alt="yyahang"/><br /><sub><b>yyahang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yyahang" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/JuJinPark"><img src="https://avatars.githubusercontent.com/u/44892459?v=4?s=100" width="100px;" alt="jujin"/><br /><sub><b>jujin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=JuJinPark" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/LL-LIN"><img src="https://avatars.githubusercontent.com/u/43002118?v=4?s=100" width="100px;" alt="LL-LIN"/><br /><sub><b>LL-LIN</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=LL-LIN" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://bigcyy.github.io/"><img src="https://avatars.githubusercontent.com/u/73413979?v=4?s=100" width="100px;" alt="Yang Chen"/><br /><sub><b>Yang Chen</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=bigcyy" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sarthakeash"><img src="https://avatars.githubusercontent.com/u/74091160?v=4?s=100" width="100px;" alt="Sarthak Arora"/><br /><sub><b>Sarthak Arora</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=sarthakeash" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -542,7 +521,7 @@ Thanks these wonderful people, welcome to join us:
|
||||
|
||||
[Reddit Community](https://www.reddit.com/r/hertzbeat/)
|
||||
|
||||
[Follow Us Twitter](https://x.com/hertzbeat1024)
|
||||
[Follow Us Twitter](https://twitter.com/hertzbeat1024)
|
||||
|
||||
[Subscribe YouTube](https://www.youtube.com/channel/UCri75zfWX0GHqJFPENEbLow)
|
||||
|
||||
|
||||
@@ -29,11 +29,6 @@
|
||||
"type": 0,
|
||||
"paramValue": 1000
|
||||
},
|
||||
{
|
||||
"field": "ssl",
|
||||
"type": 1,
|
||||
"paramValue": false
|
||||
},
|
||||
{
|
||||
"field": "username",
|
||||
"type": 1
|
||||
|
||||
@@ -73,11 +73,6 @@
|
||||
"field": "successCode",
|
||||
"type": 4,
|
||||
"paramValue": "200, 201"
|
||||
},
|
||||
{
|
||||
"field": "enableUrlEncoding",
|
||||
"type": 1,
|
||||
"paramValue": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -40,11 +40,6 @@
|
||||
<groupId>org.apache.hertzbeat</groupId>
|
||||
<artifactId>hertzbeat-plugin</artifactId>
|
||||
</dependency>
|
||||
<!-- warehouse -->
|
||||
<dependency>
|
||||
<groupId>org.apache.hertzbeat</groupId>
|
||||
<artifactId>hertzbeat-warehouse</artifactId>
|
||||
</dependency>
|
||||
<!-- spring -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
@@ -98,6 +93,25 @@
|
||||
<version>${easy-poi.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<!-- sms -->
|
||||
<dependency>
|
||||
<groupId>com.tencentcloudapi</groupId>
|
||||
<artifactId>tencentcloud-sdk-java-sms</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.squareup.okhttp</groupId>
|
||||
<artifactId>logging-interceptor</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>com.squareup.okhttp</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>com.squareup.okio</groupId>
|
||||
<artifactId>okio</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.huaweicloud.sdk</groupId>
|
||||
<artifactId>huaweicloud-sdk-smn</artifactId>
|
||||
|
||||
+50
-132
@@ -17,172 +17,90 @@
|
||||
|
||||
package org.apache.hertzbeat.alert.calculate;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Objects;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.alert.reduce.AlarmCommonReduce;
|
||||
import org.apache.hertzbeat.alert.service.DataSourceService;
|
||||
import org.apache.hertzbeat.alert.util.AlertTemplateUtil;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
|
||||
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
|
||||
import org.apache.hertzbeat.common.util.JexlExpressionRunner;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Periodic Alert Calculator
|
||||
*/
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class PeriodicAlertCalculator {
|
||||
|
||||
private static final String VALUE = "__value__";
|
||||
private static final String TIMESTAMP = "__timestamp__";
|
||||
|
||||
private final DataSourceService dataSourceService;
|
||||
private final AlarmCommonReduce alarmCommonReduce;
|
||||
/**
|
||||
* The alarm in the process is triggered
|
||||
* key - labels fingerprint
|
||||
*/
|
||||
private final Map<String, SingleAlert> pendingAlertMap;
|
||||
/**
|
||||
* The not recover alert
|
||||
* key - labels fingerprint
|
||||
*/
|
||||
private final Map<String, SingleAlert> firingAlertMap;
|
||||
private final JexlExpressionRunner expressionRunner;
|
||||
private final Map<String, SingleAlert> notRecoveredAlertMap = new ConcurrentHashMap<>(16);
|
||||
|
||||
public PeriodicAlertCalculator(DataSourceService dataSourceService, AlarmCommonReduce alarmCommonReduce) {
|
||||
this.dataSourceService = dataSourceService;
|
||||
this.alarmCommonReduce = alarmCommonReduce;
|
||||
this.pendingAlertMap = new ConcurrentHashMap<>(8);
|
||||
this.firingAlertMap = new ConcurrentHashMap<>(8);
|
||||
}
|
||||
|
||||
public void calculate(AlertDefine rule) {
|
||||
public List<SingleAlert> calculate(AlertDefine rule) {
|
||||
if (!rule.isEnable() || StringUtils.isEmpty(rule.getExpr())) {
|
||||
log.error("Periodic rule {} is disabled or expression is empty", rule.getName());
|
||||
return;
|
||||
return Collections.emptyList();
|
||||
}
|
||||
long currentTimeMilli = System.currentTimeMillis();
|
||||
// todo: implement the following logic
|
||||
try {
|
||||
// for prometheus is instant promql query, for db is sql query
|
||||
// result: [{'value': 100, 'timestamp': 1343554, 'instance': 'node1'},{'value': 200, 'timestamp': 1343555, 'instance': 'node2'}]
|
||||
// the return result should be matched with threshold
|
||||
try {
|
||||
List<Map<String, Object>> results = dataSourceService.calculate(
|
||||
rule.getDatasource(),
|
||||
rule.getExpr()
|
||||
);
|
||||
// if no match the expr threshold, the results item map {'value': null} should be null and others field keep
|
||||
// if results has multi list, should trigger multi alert
|
||||
if (CollectionUtils.isEmpty(results)) {
|
||||
return;
|
||||
}
|
||||
for (Map<String, Object> result : results) {
|
||||
Map<String, String> fingerPrints = new HashMap<>(8);
|
||||
// here use the alert name as finger, not care the alert name may be changed
|
||||
fingerPrints.put(CommonConstants.LABEL_ALERT_NAME, rule.getName());
|
||||
fingerPrints.putAll(rule.getLabels());
|
||||
for (Map.Entry<String, Object> entry : result.entrySet()) {
|
||||
if (entry.getValue() != null && !VALUE.equals(entry.getKey())
|
||||
&& !TIMESTAMP.equals(entry.getKey())) {
|
||||
fingerPrints.put(entry.getKey(), entry.getValue().toString());
|
||||
}
|
||||
}
|
||||
if (result.get(VALUE) == null) {
|
||||
// recovery the alert
|
||||
handleRecoveredAlert(fingerPrints);
|
||||
continue;
|
||||
}
|
||||
Map<String, Object> fieldValueMap = new HashMap<>(8);
|
||||
fieldValueMap.putAll(rule.getLabels());
|
||||
fieldValueMap.put(CommonConstants.LABEL_ALERT_NAME, rule.getName());
|
||||
for (Map.Entry<String, Object> entry : result.entrySet()) {
|
||||
if (entry.getValue() != null) {
|
||||
fieldValueMap.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
afterThresholdRuleMatch(currentTimeMilli, fingerPrints, fieldValueMap, rule);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
// ignore the query exception eg: no result, timeout, etc
|
||||
return;
|
||||
// Execute query
|
||||
List<Map<String, Object>> queryResults = dataSourceService.query(
|
||||
rule.getDatasource(),
|
||||
rule.getExpr()
|
||||
);
|
||||
|
||||
if (CollectionUtils.isEmpty(queryResults)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
// Execute expression calculation on query results
|
||||
List<SingleAlert> newAlerts = queryResults.stream()
|
||||
.filter(result -> execAlertExpression(result, rule.getExpr()))
|
||||
.map(result -> buildAlert(rule, result))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// Handle recovery notification
|
||||
if (newAlerts.isEmpty()) {
|
||||
return handleAlertRecover(rule);
|
||||
}
|
||||
|
||||
return newAlerts;
|
||||
} catch (Exception e) {
|
||||
log.error("Calculate periodic rule {} failed: {}", rule.getName(), e.getMessage());
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
private void afterThresholdRuleMatch(long currentTimeMilli, Map<String, String> fingerPrints,
|
||||
Map<String, Object> fieldValueMap, AlertDefine define) {
|
||||
String fingerprint = calculateFingerprint(fingerPrints);
|
||||
SingleAlert existingAlert = pendingAlertMap.get(fingerprint);
|
||||
Map<String, String> labels = new HashMap<>(8);
|
||||
fieldValueMap.putAll(define.getLabels());
|
||||
labels.putAll(fingerPrints);
|
||||
int requiredTimes = define.getTimes() == null ? 1 : define.getTimes();
|
||||
if (existingAlert == null) {
|
||||
// First time triggering alert, create new alert and set to pending status
|
||||
SingleAlert newAlert = SingleAlert.builder()
|
||||
.labels(labels)
|
||||
// todo render var content in annotations
|
||||
.annotations(define.getAnnotations())
|
||||
.content(AlertTemplateUtil.render(define.getTemplate(), fieldValueMap))
|
||||
.status(CommonConstants.ALERT_STATUS_PENDING)
|
||||
.triggerTimes(1)
|
||||
.startAt(currentTimeMilli)
|
||||
.activeAt(currentTimeMilli)
|
||||
.build();
|
||||
|
||||
// If required trigger times is 1, set to firing status directly
|
||||
if (requiredTimes <= 1) {
|
||||
newAlert.setStatus(CommonConstants.ALERT_STATUS_FIRING);
|
||||
firingAlertMap.put(fingerprint, newAlert);
|
||||
alarmCommonReduce.reduceAndSendAlarm(newAlert.clone());
|
||||
} else {
|
||||
// Otherwise put into pending queue first
|
||||
pendingAlertMap.put(fingerprint, newAlert);
|
||||
}
|
||||
} else {
|
||||
// Update existing alert
|
||||
existingAlert.setTriggerTimes(existingAlert.getTriggerTimes() + 1);
|
||||
existingAlert.setActiveAt(currentTimeMilli);
|
||||
|
||||
// Check if required trigger times reached
|
||||
if (existingAlert.getStatus().equals(CommonConstants.ALERT_STATUS_PENDING) && existingAlert.getTriggerTimes() >= requiredTimes) {
|
||||
// Reached trigger times threshold, change to firing status
|
||||
pendingAlertMap.remove(fingerprint);
|
||||
existingAlert.setStatus(CommonConstants.ALERT_STATUS_FIRING);
|
||||
firingAlertMap.put(fingerprint, existingAlert);
|
||||
alarmCommonReduce.reduceAndSendAlarm(existingAlert.clone());
|
||||
}
|
||||
}
|
||||
private boolean execAlertExpression(Map<String, Object> result, String expr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void handleRecoveredAlert(Map<String, String> fingerprints) {
|
||||
String fingerprint = calculateFingerprint(fingerprints);
|
||||
SingleAlert firingAlert = firingAlertMap.remove(fingerprint);
|
||||
private SingleAlert buildAlert(AlertDefine rule, Map<String, Object> metrics) {
|
||||
return SingleAlert.builder()
|
||||
.labels(rule.getLabels())
|
||||
.annotations(rule.getAnnotations())
|
||||
.triggerTimes(1)
|
||||
.startAt(System.currentTimeMillis())
|
||||
.activeAt(System.currentTimeMillis())
|
||||
.build();
|
||||
}
|
||||
|
||||
private List<SingleAlert> handleAlertRecover(AlertDefine rule) {
|
||||
SingleAlert firingAlert = notRecoveredAlertMap.remove(rule.getId().toString());
|
||||
if (firingAlert != null) {
|
||||
// todo consider multi times to tig for resolved alert
|
||||
firingAlert.setTriggerTimes(1);
|
||||
firingAlert.setEndAt(System.currentTimeMillis());
|
||||
firingAlert.setStatus(CommonConstants.ALERT_STATUS_RESOLVED);
|
||||
alarmCommonReduce.reduceAndSendAlarm(firingAlert.clone());
|
||||
return Collections.singletonList(buildResolvedAlert(rule, firingAlert));
|
||||
}
|
||||
pendingAlertMap.remove(fingerprint);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private String calculateFingerprint(Map<String, String> fingerPrints) {
|
||||
List<String> keyList = fingerPrints.keySet().stream().filter(Objects::nonNull).sorted().toList();
|
||||
List<String> valueList = fingerPrints.values().stream().filter(Objects::nonNull).sorted().toList();
|
||||
return Arrays.hashCode(keyList.toArray(new String[0])) + "-"
|
||||
+ Arrays.hashCode(valueList.toArray(new String[0]));
|
||||
private SingleAlert buildResolvedAlert(AlertDefine rule, SingleAlert firingAlert) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
+4
-70
@@ -17,79 +17,13 @@
|
||||
|
||||
package org.apache.hertzbeat.alert.calculate;
|
||||
|
||||
import static org.apache.hertzbeat.common.constants.CommonConstants.ALERT_THRESHOLD_TYPE_PERIODIC;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.alert.dao.AlertDefineDao;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
|
||||
|
||||
/**
|
||||
* Periodic Alert Rule Scheduler
|
||||
* period alert rule scheduler
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class PeriodicAlertRuleScheduler implements CommandLineRunner {
|
||||
|
||||
private final PeriodicAlertCalculator calculator;
|
||||
private final AlertDefineDao alertDefineDao;
|
||||
private final ScheduledExecutorService scheduledExecutor;
|
||||
private final Map<Long, ScheduledFuture<?>> scheduledFutures;
|
||||
|
||||
public PeriodicAlertRuleScheduler(PeriodicAlertCalculator calculator, AlertDefineDao alertDefineDao) {
|
||||
this.calculator = calculator;
|
||||
this.alertDefineDao = alertDefineDao;
|
||||
ThreadFactory threadFactory = new ThreadFactoryBuilder()
|
||||
.setUncaughtExceptionHandler((thread, throwable) -> {
|
||||
log.error("Scheduled periodic alert threshold has uncaughtException.");
|
||||
log.error(throwable.getMessage(), throwable);
|
||||
})
|
||||
.setDaemon(true)
|
||||
.setNameFormat("periodic-alert-threshold-worker-%d")
|
||||
.build();
|
||||
this.scheduledExecutor = Executors.newScheduledThreadPool(10, threadFactory);
|
||||
this.scheduledFutures = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
public void cancelSchedule(Long ruleId) {
|
||||
if (ruleId == null) {
|
||||
return;
|
||||
}
|
||||
ScheduledFuture<?> future = scheduledFutures.get(ruleId);
|
||||
if (future != null) {
|
||||
future.cancel(true);
|
||||
scheduledFutures.remove(ruleId);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateSchedule(AlertDefine rule) {
|
||||
if (rule == null || rule.getId() == null) {
|
||||
log.error("Alert rule is null or rule id is null.");
|
||||
return;
|
||||
}
|
||||
cancelSchedule(rule.getId());
|
||||
if (rule.getType().equals(ALERT_THRESHOLD_TYPE_PERIODIC)) {
|
||||
ScheduledFuture<?> future = scheduledExecutor.scheduleAtFixedRate(() -> {
|
||||
calculator.calculate(rule);
|
||||
}, 0, rule.getPeriod(), java.util.concurrent.TimeUnit.SECONDS);
|
||||
scheduledFutures.put(rule.getId(), future);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(String... args) throws Exception {
|
||||
log.info("Starting periodic alert rule scheduler...");
|
||||
List<AlertDefine> periodicRules = alertDefineDao.findAlertDefinesByTypeAndEnableTrue(ALERT_THRESHOLD_TYPE_PERIODIC);
|
||||
for (AlertDefine rule : periodicRules) {
|
||||
updateSchedule(rule);
|
||||
}
|
||||
}
|
||||
public class PeriodicAlertRuleScheduler {
|
||||
|
||||
// todo implement the following logic
|
||||
}
|
||||
|
||||
+18
-43
@@ -55,8 +55,6 @@ public class RealTimeAlertCalculator {
|
||||
private static final int CALCULATE_THREADS = 3;
|
||||
|
||||
private static final String KEY_INSTANCE = "__instance__";
|
||||
private static final String KEY_INSTANCE_NAME = "__instancename__";
|
||||
private static final String KEY_INSTANCE_HOST = "__instancehost__";
|
||||
private static final String KEY_APP = "__app__";
|
||||
private static final String KEY_METRICS = "__metrics__";
|
||||
private static final String KEY_PRIORITY = "__priority__";
|
||||
@@ -127,14 +125,10 @@ public class RealTimeAlertCalculator {
|
||||
private void calculate(CollectRep.MetricsData metricsData) {
|
||||
long currentTimeMilli = System.currentTimeMillis();
|
||||
String instance = String.valueOf(metricsData.getId());
|
||||
String instanceName = metricsData.getInstanceName();
|
||||
String instanceHost = metricsData.getInstanceHost();
|
||||
String app = metricsData.getApp();
|
||||
String metrics = metricsData.getMetrics();
|
||||
int priority = metricsData.getPriority();
|
||||
int code = metricsData.getCode().getNumber();
|
||||
Map<String, String> labels = metricsData.getLabels();
|
||||
Map<String, String> annotations = metricsData.getAnnotations();
|
||||
List<AlertDefine> thresholds = this.alertDefineService.getRealTimeAlertDefines();
|
||||
// Filter thresholds by app, metrics and instance
|
||||
thresholds = filterThresholdsByAppAndMetrics(thresholds, app, metrics, instance, priority);
|
||||
@@ -143,8 +137,6 @@ public class RealTimeAlertCalculator {
|
||||
}
|
||||
Map<String, Object> commonContext = new HashMap<>(8);
|
||||
commonContext.put(KEY_INSTANCE, instance);
|
||||
commonContext.put(KEY_INSTANCE_NAME, instanceName);
|
||||
commonContext.put(KEY_INSTANCE_HOST, instanceHost);
|
||||
commonContext.put(KEY_APP, app);
|
||||
commonContext.put(KEY_PRIORITY, priority);
|
||||
commonContext.put(KEY_CODE, code);
|
||||
@@ -152,9 +144,6 @@ public class RealTimeAlertCalculator {
|
||||
if (priority == 0) {
|
||||
commonContext.put(KEY_AVAILABLE, metricsData.getCode() == CollectRep.Code.SUCCESS ? UP : DOWN);
|
||||
}
|
||||
if (labels != null) {
|
||||
commonContext.putAll(labels);
|
||||
}
|
||||
List<CollectRep.Field> fields = metricsData.getFields();
|
||||
Map<String, Object> fieldValueMap = new HashMap<>(8);
|
||||
int valueRowCount = metricsData.getValuesCount();
|
||||
@@ -171,26 +160,21 @@ public class RealTimeAlertCalculator {
|
||||
if (StringUtils.isBlank(expr)) {
|
||||
continue;
|
||||
}
|
||||
Map<String, String> commonFingerPrints = new HashMap<>(8);
|
||||
commonFingerPrints.put(CommonConstants.LABEL_INSTANCE, instance);
|
||||
// here use the alert name as finger, not care the alert name may be changed
|
||||
commonFingerPrints.put(CommonConstants.LABEL_ALERT_NAME, define.getName());
|
||||
commonFingerPrints.put(CommonConstants.LABEL_INSTANCE_NAME, instanceName);
|
||||
commonFingerPrints.put(CommonConstants.LABEL_INSTANCE_HOST, instanceHost);
|
||||
commonFingerPrints.putAll(define.getLabels());
|
||||
if (labels != null) {
|
||||
commonFingerPrints.putAll(labels);
|
||||
}
|
||||
{
|
||||
// trigger the expr before the metrics data, due the available up down or others
|
||||
try {
|
||||
boolean match = execAlertExpression(fieldValueMap, expr, true);
|
||||
try {
|
||||
Map<String, String> fingerPrints = new HashMap<>(8);
|
||||
fingerPrints.put(CommonConstants.LABEL_INSTANCE, instance);
|
||||
// here use the alert name as finger, not care the alert name may be changed
|
||||
fingerPrints.put(CommonConstants.LABEL_ALERT_NAME, define.getName());
|
||||
fingerPrints.putAll(define.getLabels());
|
||||
if (match) {
|
||||
// If the threshold rule matches, the number of times the threshold has been triggered is determined and an alarm is triggered
|
||||
afterThresholdRuleMatch(currentTimeMilli, commonFingerPrints, fieldValueMap, define, annotations);
|
||||
afterThresholdRuleMatch(currentTimeMilli, fingerPrints, fieldValueMap, define);
|
||||
} else {
|
||||
handleRecoveredAlert(commonFingerPrints);
|
||||
handleRecoveredAlert(fingerPrints);
|
||||
}
|
||||
// if this threshold pre compile success, ignore blew
|
||||
continue;
|
||||
@@ -208,7 +192,9 @@ public class RealTimeAlertCalculator {
|
||||
fieldValueMap.put(KEY_ROW, valueRowCount);
|
||||
fieldValueMap.putAll(commonContext);
|
||||
fingerPrints.clear();
|
||||
fingerPrints.putAll(commonFingerPrints);
|
||||
fingerPrints.put(CommonConstants.LABEL_INSTANCE, instance);
|
||||
fingerPrints.put(CommonConstants.LABEL_ALERT_NAME, define.getName());
|
||||
fingerPrints.putAll(define.getLabels());
|
||||
for (int index = 0; index < valueRow.getColumnsList().size(); index++) {
|
||||
String valueStr = valueRow.getColumns(index);
|
||||
if (CommonConstants.NULL_VALUE.equals(valueStr)) {
|
||||
@@ -242,7 +228,7 @@ public class RealTimeAlertCalculator {
|
||||
boolean match = execAlertExpression(fieldValueMap, expr, false);
|
||||
try {
|
||||
if (match) {
|
||||
afterThresholdRuleMatch(currentTimeMilli, fingerPrints, fieldValueMap, define, annotations);
|
||||
afterThresholdRuleMatch(currentTimeMilli, fingerPrints, fieldValueMap, define);
|
||||
} else {
|
||||
handleRecoveredAlert(fingerPrints);
|
||||
}
|
||||
@@ -325,31 +311,20 @@ public class RealTimeAlertCalculator {
|
||||
pendingAlertMap.remove(fingerprint);
|
||||
}
|
||||
|
||||
private void afterThresholdRuleMatch(long currentTimeMilli, Map<String, String> fingerPrints,
|
||||
Map<String, Object> fieldValueMap, AlertDefine define, Map<String, String> annotations) {
|
||||
private void afterThresholdRuleMatch(long currentTimeMilli, Map<String, String> fingerPrints,
|
||||
Map<String, Object> fieldValueMap, AlertDefine define) {
|
||||
String fingerprint = calculateFingerprint(fingerPrints);
|
||||
SingleAlert existingAlert = pendingAlertMap.get(fingerprint);
|
||||
Map<String, String> labels = new HashMap<>(8);
|
||||
fieldValueMap.putAll(define.getLabels());
|
||||
labels.putAll(fingerPrints);
|
||||
int requiredTimes = define.getTimes() == null ? 1 : define.getTimes();
|
||||
if (existingAlert == null) {
|
||||
// First time triggering alert, create new alert and set to pending status
|
||||
Map<String, String> alertLabels = new HashMap<>(8);
|
||||
alertLabels.putAll(fingerPrints);
|
||||
Map<String, String> alertAnnotations = new HashMap<>(8);
|
||||
if (annotations != null) {
|
||||
alertAnnotations.putAll(annotations);
|
||||
}
|
||||
if (define.getAnnotations() != null) {
|
||||
alertAnnotations.putAll(define.getAnnotations());
|
||||
}
|
||||
// render var content in annotations
|
||||
for (Map.Entry<String, String> entry : alertAnnotations.entrySet()) {
|
||||
entry.setValue(AlertTemplateUtil.render(entry.getValue(), fieldValueMap));
|
||||
}
|
||||
SingleAlert newAlert = SingleAlert.builder()
|
||||
.labels(alertLabels)
|
||||
.annotations(alertAnnotations)
|
||||
// render var content in content
|
||||
.labels(labels)
|
||||
// todo render var content in annotations
|
||||
.annotations(define.getAnnotations())
|
||||
.content(AlertTemplateUtil.render(define.getTemplate(), fieldValueMap))
|
||||
.status(CommonConstants.ALERT_STATUS_PENDING)
|
||||
.triggerTimes(1)
|
||||
|
||||
-70
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
* 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.config;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* SSE manager for alert
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class AlertSseManager {
|
||||
private final Map<Long, SseEmitter> emitters = new ConcurrentHashMap<>();
|
||||
|
||||
public SseEmitter createEmitter(Long clientId) {
|
||||
SseEmitter emitter = new SseEmitter(Long.MAX_VALUE);
|
||||
emitter.onCompletion(() -> removeEmitter(clientId));
|
||||
emitter.onTimeout(() -> removeEmitter(clientId));
|
||||
emitter.onError((ex) -> removeEmitter(clientId));
|
||||
emitters.put(clientId, emitter);
|
||||
return emitter;
|
||||
}
|
||||
|
||||
@Async
|
||||
public void broadcast(String data) {
|
||||
emitters.forEach((clientId, emitter) -> {
|
||||
try {
|
||||
emitter.send(SseEmitter.event()
|
||||
.id(String.valueOf(System.currentTimeMillis()))
|
||||
.name("ALERT_EVENT")
|
||||
.data(data));
|
||||
} catch (IOException | IllegalStateException e) {
|
||||
emitter.complete();
|
||||
removeEmitter(clientId);
|
||||
} catch (Exception exception) {
|
||||
log.error("Failed to broadcast alert data to client: {}", exception.getMessage());
|
||||
emitter.complete();
|
||||
removeEmitter(clientId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void removeEmitter(Long clientId) {
|
||||
emitters.remove(clientId);
|
||||
}
|
||||
}
|
||||
-55
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* 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.config;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Alibaba Cloud SMS configuration properties
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class AlibabaSmsProperties {
|
||||
/**
|
||||
* Alibaba Cloud access key id
|
||||
*/
|
||||
@NotBlank(message = "AccessKeyId cannot be empty")
|
||||
private String accessKeyId;
|
||||
|
||||
/**
|
||||
* Alibaba Cloud access key secret
|
||||
*/
|
||||
@NotBlank(message = "AccessKeySecret cannot be empty")
|
||||
private String accessKeySecret;
|
||||
|
||||
/**
|
||||
* SMS signature
|
||||
*/
|
||||
@NotBlank(message = "SignName cannot be empty")
|
||||
private String signName;
|
||||
|
||||
/**
|
||||
* SMS template Code
|
||||
*/
|
||||
@NotBlank(message = "TemplateCode cannot be null")
|
||||
private String templateCode;
|
||||
}
|
||||
-46
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* 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.config;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* AWS Cloud SMS configuration properties
|
||||
*/
|
||||
@Data
|
||||
public class AwsSmsProperties {
|
||||
/**
|
||||
* AWS Cloud access key id
|
||||
*/
|
||||
@NotBlank(message = "AccessKeyId cannot be empty")
|
||||
private String accessKeyId;
|
||||
|
||||
/**
|
||||
* AWS Cloud access key secret
|
||||
*/
|
||||
@NotBlank(message = "AccessKeySecret cannot be empty")
|
||||
private String accessKeySecret;
|
||||
|
||||
/**
|
||||
* AWS Cloud region
|
||||
*/
|
||||
@NotBlank(message = "region cannot be empty")
|
||||
private String region;
|
||||
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
* 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.config;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* SMS configuration
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "alerter.sms")
|
||||
public class SmsConfig {
|
||||
|
||||
/**
|
||||
* whether to enable SMS, default is false
|
||||
*/
|
||||
private boolean enable = false;
|
||||
|
||||
/**
|
||||
* sms service provider
|
||||
*/
|
||||
@NotBlank(message = "Type cannot be empty")
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* Tencent cloud SMS configuration
|
||||
*/
|
||||
private TencentSmsProperties tencent;
|
||||
|
||||
/**
|
||||
* Aliyun SMS configuration
|
||||
*/
|
||||
private AlibabaSmsProperties alibaba;
|
||||
|
||||
/**
|
||||
* UniSMS configuration
|
||||
*/
|
||||
private UniSmsProperties unisms;
|
||||
|
||||
/**
|
||||
* Aws configuration
|
||||
*/
|
||||
private AwsSmsProperties aws;
|
||||
|
||||
/**
|
||||
* Twilio SMS configuration
|
||||
*/
|
||||
private TwilioSmsProperties twilio;
|
||||
|
||||
/**
|
||||
* Smslocal SMS configuration
|
||||
*/
|
||||
private SmslocalSmsProperties smslocal;
|
||||
}
|
||||
-49
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* 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.config;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Twilio SMS configuration properties
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class TwilioSmsProperties {
|
||||
/**
|
||||
* Twilio Account SID
|
||||
*/
|
||||
@NotBlank(message = "Account SID cannot be empty")
|
||||
private String accountSid;
|
||||
|
||||
/**
|
||||
* Twilio Auth Token
|
||||
*/
|
||||
@NotBlank(message = "Auth Token cannot be empty")
|
||||
private String authToken;
|
||||
|
||||
/**
|
||||
* Twilio Issued Phone Number
|
||||
*/
|
||||
@NotBlank(message = "Twilio Phone Number cannot be empty")
|
||||
private String twilioPhoneNumber;
|
||||
}
|
||||
-60
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* 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.config;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* UniSMS properties
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class UniSmsProperties {
|
||||
/**
|
||||
* UniSMS access key id
|
||||
*/
|
||||
@NotBlank(message = "accessKeyId cannot be empty")
|
||||
private String accessKeyId;
|
||||
|
||||
/**
|
||||
* UniSMS access key secret, required for HMAC mode
|
||||
*/
|
||||
private String accessKeySecret;
|
||||
|
||||
/**
|
||||
* SMS signature
|
||||
*/
|
||||
@NotBlank(message = "signature cannot be null")
|
||||
private String signature;
|
||||
|
||||
/**
|
||||
* SMS template ID
|
||||
*/
|
||||
@NotBlank(message = "templateId cannot be null")
|
||||
private String templateId;
|
||||
|
||||
/**
|
||||
* Authentication mode: simple or hmac, default is simple
|
||||
*/
|
||||
@NotBlank(message = "authMode cannot be null")
|
||||
private String authMode = "simple";
|
||||
}
|
||||
+7
-7
@@ -38,9 +38,9 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* Alarm Inhibit management API
|
||||
* Alarm Silence management API
|
||||
*/
|
||||
@Tag(name = "Alert Inhibit API")
|
||||
@Tag(name = "Alert Silence API")
|
||||
@RestController
|
||||
@RequestMapping(path = "/api/alert/inhibit", produces = {APPLICATION_JSON_VALUE})
|
||||
public class AlertInhibitController {
|
||||
@@ -49,7 +49,7 @@ public class AlertInhibitController {
|
||||
private AlertInhibitService alertInhibitService;
|
||||
|
||||
@PostMapping
|
||||
@Operation(summary = "New Alarm Inhibit", description = "Added an alarm Inhibit")
|
||||
@Operation(summary = "New Alarm Silence", description = "Added an alarm Silence")
|
||||
public ResponseEntity<Message<Void>> addNewAlertInhibit(@Valid @RequestBody AlertInhibit alertInhibit) {
|
||||
alertInhibitService.validate(alertInhibit, false);
|
||||
alertInhibitService.addAlertInhibit(alertInhibit);
|
||||
@@ -57,7 +57,7 @@ public class AlertInhibitController {
|
||||
}
|
||||
|
||||
@PutMapping
|
||||
@Operation(summary = "Modifying an Alarm Inhibit", description = "Modify an existing alarm Inhibit")
|
||||
@Operation(summary = "Modifying an Alarm Silence", description = "Modify an existing alarm Silence")
|
||||
public ResponseEntity<Message<Void>> modifyAlertInhibit(@Valid @RequestBody AlertInhibit alertInhibit) {
|
||||
alertInhibitService.validate(alertInhibit, true);
|
||||
alertInhibitService.modifyAlertInhibit(alertInhibit);
|
||||
@@ -65,10 +65,10 @@ public class AlertInhibitController {
|
||||
}
|
||||
|
||||
@GetMapping(path = "/{id}")
|
||||
@Operation(summary = "Querying Alarm Inhibit",
|
||||
description = "You can obtain alarm Inhibit information based on the alarm Inhibit ID")
|
||||
@Operation(summary = "Querying Alarm Silence",
|
||||
description = "You can obtain alarm Silence information based on the alarm Silence ID")
|
||||
public ResponseEntity<Message<AlertInhibit>> getAlertInhibit(
|
||||
@Parameter(description = "Alarm Inhibit ID", example = "6565463543") @PathVariable("id") long id) {
|
||||
@Parameter(description = "Alarm Silence ID", example = "6565463543") @PathVariable("id") long id) {
|
||||
AlertInhibit alertInhibit = alertInhibitService.getAlertInhibit(id);
|
||||
|
||||
return Objects.isNull(alertInhibit)
|
||||
|
||||
+4
-4
@@ -36,9 +36,9 @@ import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* Inhibit the batch API for alarms
|
||||
* Silence the batch API for alarms
|
||||
*/
|
||||
@Tag(name = "Alert Inhibit Batch API")
|
||||
@Tag(name = "Alert Silence Batch API")
|
||||
@RestController
|
||||
@RequestMapping(path = "/api/alert/inhibits", produces = {APPLICATION_JSON_VALUE})
|
||||
public class AlertInhibitsController {
|
||||
@@ -50,7 +50,7 @@ public class AlertInhibitsController {
|
||||
@Operation(summary = "Query the alarm inhibit list",
|
||||
description = "You can obtain the list of alarm inhibit by querying filter items")
|
||||
public ResponseEntity<Message<Page<AlertInhibit>>> getAlertInhibits(
|
||||
@Parameter(description = "Alarm Inhibit ID", example = "6565463543") @RequestParam(required = false) List<Long> ids,
|
||||
@Parameter(description = "Alarm Silence ID", example = "6565463543") @RequestParam(required = false) List<Long> ids,
|
||||
@Parameter(description = "Search Name", example = "x") @RequestParam(required = false) String search,
|
||||
@Parameter(description = "Sort field, default id", example = "id") @RequestParam(defaultValue = "id") String sort,
|
||||
@Parameter(description = "Sort mode: asc: ascending, desc: descending", example = "desc") @RequestParam(defaultValue = "desc") String order,
|
||||
@@ -64,7 +64,7 @@ public class AlertInhibitsController {
|
||||
@Operation(summary = "Delete alarm inhibit in batches",
|
||||
description = "Delete alarm inhibit in batches based on the alarm inhibit ID list")
|
||||
public ResponseEntity<Message<Void>> deleteAlertDefines(
|
||||
@Parameter(description = "Alarm Inhibit IDs", example = "6565463543") @RequestParam(required = false) List<Long> ids
|
||||
@Parameter(description = "Alarm Silence IDs", example = "6565463543") @RequestParam(required = false) List<Long> ids
|
||||
) {
|
||||
if (ids != null && !ids.isEmpty()) {
|
||||
alertInhibitService.deleteAlertInhibits(new HashSet<>(ids));
|
||||
|
||||
-48
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* 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.controller;
|
||||
|
||||
import static org.springframework.http.MediaType.TEXT_EVENT_STREAM_VALUE;
|
||||
import org.apache.hertzbeat.alert.config.AlertSseManager;
|
||||
import org.apache.hertzbeat.common.util.SnowFlakeIdGenerator;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
/**
|
||||
* SSE controller for alert
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping(path = "/api/alert/sse", produces = {TEXT_EVENT_STREAM_VALUE})
|
||||
public class AlertSseController {
|
||||
|
||||
private final AlertSseManager emitterManager;
|
||||
|
||||
public AlertSseController(AlertSseManager emitterManager) {
|
||||
this.emitterManager = emitterManager;
|
||||
}
|
||||
|
||||
@GetMapping(path = "/subscribe")
|
||||
public SseEmitter subscribe() {
|
||||
Long clientId = SnowFlakeIdGenerator.generateId();
|
||||
return emitterManager.createEmitter(clientId);
|
||||
}
|
||||
}
|
||||
+9
-31
@@ -31,7 +31,6 @@ import org.apache.hertzbeat.common.entity.alerter.NoticeRule;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
|
||||
import org.apache.hertzbeat.alert.service.NoticeConfigService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
@@ -83,18 +82,9 @@ public class NoticeConfigController {
|
||||
@GetMapping(path = "/receivers")
|
||||
@Operation(summary = "Get a list of message notification recipients based on query filter items",
|
||||
description = "Get a list of message notification recipients based on query filter items")
|
||||
public ResponseEntity<Message<Page<NoticeReceiver>>> getReceivers(
|
||||
@Parameter(description = "en: Recipient name,support fuzzy query", example = "tom") @RequestParam(required = false) final String name,
|
||||
@Parameter(description = "en: List current page", example = "0") @RequestParam(defaultValue = "0") final int pageIndex,
|
||||
@Parameter(description = "en: Number of list pages", example = "8") @RequestParam(defaultValue = "8") final int pageSize) {
|
||||
return ResponseEntity.ok(Message.success(noticeConfigService.getNoticeReceivers(name, pageIndex, pageSize)));
|
||||
}
|
||||
|
||||
@GetMapping(path = "/receivers/all")
|
||||
@Operation(summary = "Get a list of all message notification recipients",
|
||||
description = "Get a list of all message notification recipients")
|
||||
public ResponseEntity<Message<List<NoticeReceiver>>> getAllReceivers() {
|
||||
return ResponseEntity.ok(Message.success(noticeConfigService.getAllNoticeReceivers()));
|
||||
public ResponseEntity<Message<List<NoticeReceiver>>> getReceivers(
|
||||
@Parameter(description = "en: Recipient name,support fuzzy query", example = "tom") @RequestParam(required = false) final String name) {
|
||||
return ResponseEntity.ok(Message.success(noticeConfigService.getNoticeReceivers(name)));
|
||||
}
|
||||
|
||||
@GetMapping(path = "/receiver/{id}")
|
||||
@@ -139,11 +129,9 @@ public class NoticeConfigController {
|
||||
@GetMapping(path = "/rules")
|
||||
@Operation(summary = "Get a list of message notification policies based on query filter items",
|
||||
description = "Get a list of message notification policies based on query filter items")
|
||||
public ResponseEntity<Message<Page<NoticeRule>>> getRules(
|
||||
@Parameter(description = "en: Recipient name", example = "rule1") @RequestParam(required = false) final String name,
|
||||
@Parameter(description = "en: List current page", example = "0") @RequestParam(defaultValue = "0") final int pageIndex,
|
||||
@Parameter(description = "en: Number of list pages", example = "8") @RequestParam(defaultValue = "8") final int pageSize) {
|
||||
return ResponseEntity.ok(Message.success(noticeConfigService.getNoticeRules(name, pageIndex, pageSize)));
|
||||
public ResponseEntity<Message<List<NoticeRule>>> getRules(
|
||||
@Parameter(description = "en: Recipient name", example = "rule1") @RequestParam(required = false) final String name) {
|
||||
return ResponseEntity.ok(Message.success(noticeConfigService.getNoticeRules(name)));
|
||||
}
|
||||
|
||||
@GetMapping(path = "/rule/{id}")
|
||||
@@ -188,22 +176,12 @@ public class NoticeConfigController {
|
||||
@GetMapping(path = "/templates")
|
||||
@Operation(summary = "Get a list of message notification templates based on query filter items",
|
||||
description = "Get a list of message notification templates based on query filter items")
|
||||
public ResponseEntity<Message<Page<NoticeTemplate>>> getTemplates(
|
||||
@Parameter(description = "Template name,support fuzzy query", example = "rule1") @RequestParam(required = false) final String name,
|
||||
@Parameter(description = "Whether it is a preset template", example = "true") @RequestParam(defaultValue = "true") final boolean preset,
|
||||
@Parameter(description = "List current page", example = "0") @RequestParam(defaultValue = "0") final int pageIndex,
|
||||
@Parameter(description = "Number of list pages", example = "8") @RequestParam(defaultValue = "8") final int pageSize) {
|
||||
Page<NoticeTemplate> templatePage = noticeConfigService.getNoticeTemplates(name, preset, pageIndex, pageSize);
|
||||
public ResponseEntity<Message<List<NoticeTemplate>>> getTemplates(
|
||||
@Parameter(description = "Template name,support fuzzy query", example = "rule1") @RequestParam(required = false) final String name) {
|
||||
List<NoticeTemplate> templatePage = noticeConfigService.getNoticeTemplates(name);
|
||||
return ResponseEntity.ok(Message.success(templatePage));
|
||||
}
|
||||
|
||||
@GetMapping(path = "/templates/all")
|
||||
@Operation(summary = "Get a list of all message notification templates",
|
||||
description = "Get a list of all message notification templates")
|
||||
public ResponseEntity<Message<List<NoticeTemplate>>> getAllTemplates() {
|
||||
return ResponseEntity.ok(Message.success(noticeConfigService.getAllNoticeTemplates()));
|
||||
}
|
||||
|
||||
@GetMapping(path = "/template/{id}")
|
||||
@Operation(summary = "Get the notification template information based on the template ID",
|
||||
description = "Get the notification template information based on the template ID")
|
||||
|
||||
-66
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* 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 lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* SkyWalking Alert Content Entity
|
||||
* @see <a href="https://skywalking.apache.org/docs/main/latest/en/setup/backend/backend-alarm/">backend-alarm</a>
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class SkyWalkingExternAlert {
|
||||
|
||||
private Long scopeId;
|
||||
|
||||
private String scope;
|
||||
|
||||
private String name;
|
||||
|
||||
private String id0;
|
||||
|
||||
private String id1;
|
||||
|
||||
private String ruleName;
|
||||
|
||||
private String alarmMessage;
|
||||
|
||||
private Long startTime;
|
||||
|
||||
private List<Tag> tags;
|
||||
|
||||
/**
|
||||
* SkyWalking Tag Entity
|
||||
*/
|
||||
@Data
|
||||
public static class Tag {
|
||||
|
||||
private String key;
|
||||
|
||||
private String value;
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -118,11 +118,11 @@ public class TencentCloudExternAlert {
|
||||
/**
|
||||
* Transaction alarm
|
||||
*/
|
||||
public static final String EVENT = "event";
|
||||
private static final String EVENT = "event";
|
||||
|
||||
/**
|
||||
* Indicator alarm
|
||||
*/
|
||||
public static final String METRIC = "metric";
|
||||
private static final String METRIC = "metric";
|
||||
|
||||
}
|
||||
|
||||
+17
-21
@@ -23,13 +23,11 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.alert.AlerterWorkerPool;
|
||||
import org.apache.hertzbeat.alert.config.AlertSseManager;
|
||||
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeRule;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
|
||||
import org.apache.hertzbeat.alert.service.NoticeConfigService;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.apache.hertzbeat.plugin.PostAlertPlugin;
|
||||
import org.apache.hertzbeat.plugin.Plugin;
|
||||
import org.apache.hertzbeat.plugin.runner.PluginRunner;
|
||||
@@ -47,18 +45,16 @@ public class AlertNoticeDispatch {
|
||||
private final AlertStoreHandler alertStoreHandler;
|
||||
private final Map<Byte, AlertNotifyHandler> alertNotifyHandlerMap;
|
||||
private final PluginRunner pluginRunner;
|
||||
private final AlertSseManager emitterManager;
|
||||
|
||||
public AlertNoticeDispatch(AlerterWorkerPool workerPool,
|
||||
NoticeConfigService noticeConfigService,
|
||||
AlertStoreHandler alertStoreHandler,
|
||||
List<AlertNotifyHandler> alertNotifyHandlerList, PluginRunner pluginRunner, AlertSseManager emitterManager) {
|
||||
List<AlertNotifyHandler> alertNotifyHandlerList, PluginRunner pluginRunner) {
|
||||
this.workerPool = workerPool;
|
||||
this.noticeConfigService = noticeConfigService;
|
||||
this.alertStoreHandler = alertStoreHandler;
|
||||
this.pluginRunner = pluginRunner;
|
||||
alertNotifyHandlerMap = Maps.newHashMapWithExpectedSize(alertNotifyHandlerList.size());
|
||||
this.emitterManager = emitterManager;
|
||||
alertNotifyHandlerList.forEach(r -> alertNotifyHandlerMap.put(r.type(), r));
|
||||
}
|
||||
|
||||
@@ -80,7 +76,7 @@ public class AlertNoticeDispatch {
|
||||
if (noticeTemplate == null) {
|
||||
noticeTemplate = noticeConfigService.getDefaultNoticeTemplateByType(alertNotifyHandler.type());
|
||||
}
|
||||
if (noticeTemplate == null && alertNotifyHandler.type() != 0) {
|
||||
if (noticeTemplate == null) {
|
||||
log.error("alert does not have mapping default notice template. type: {}.", alertNotifyHandler.type());
|
||||
throw new NullPointerException(alertNotifyHandler.type() + " does not have mapping default notice template");
|
||||
}
|
||||
@@ -108,27 +104,27 @@ public class AlertNoticeDispatch {
|
||||
public void dispatchAlarm(GroupAlert groupAlert) {
|
||||
if (groupAlert != null) {
|
||||
// Determining alarm type storage
|
||||
GroupAlert storedGroupAlert = alertStoreHandler.store(groupAlert);
|
||||
alertStoreHandler.store(groupAlert);
|
||||
// Notice distribution
|
||||
sendNotify(storedGroupAlert);
|
||||
sendNotify(groupAlert);
|
||||
// Execute the plugin if enable (Compatible with old version plugins, will be removed in later versions)
|
||||
pluginRunner.pluginExecute(Plugin.class, plugin -> plugin.alert(storedGroupAlert));
|
||||
pluginRunner.pluginExecute(Plugin.class, plugin -> plugin.alert(groupAlert));
|
||||
// Execute the plugin if enable with params
|
||||
pluginRunner.pluginExecute(PostAlertPlugin.class, (afterAlertPlugin, pluginContext) -> afterAlertPlugin.execute(storedGroupAlert, pluginContext));
|
||||
// Send alert to the sse client
|
||||
emitterManager.broadcast(JsonUtil.toJson(storedGroupAlert));
|
||||
pluginRunner.pluginExecute(PostAlertPlugin.class, (afterAlertPlugin, pluginContext) -> afterAlertPlugin.execute(groupAlert, pluginContext));
|
||||
}
|
||||
}
|
||||
|
||||
private void sendNotify(GroupAlert alert) {
|
||||
matchNoticeRulesByAlert(alert).ifPresent(noticeRules -> noticeRules.forEach(rule -> workerPool.executeNotify(() -> rule.getReceiverId()
|
||||
.forEach(receiverId -> {
|
||||
try {
|
||||
sendNoticeMsg(getOneReceiverById(receiverId),
|
||||
getOneTemplateById(rule.getTemplateId()), alert);
|
||||
} catch (AlertNoticeException e) {
|
||||
log.warn("DispatchTask sendNoticeMsg error, message: {}", e.getMessage());
|
||||
}
|
||||
}))));
|
||||
matchNoticeRulesByAlert(alert).ifPresent(noticeRules -> noticeRules.forEach(rule -> {
|
||||
workerPool.executeNotify(() -> rule.getReceiverId()
|
||||
.forEach(receiverId -> {
|
||||
try {
|
||||
sendNoticeMsg(getOneReceiverById(receiverId),
|
||||
getOneTemplateById(rule.getTemplateId()), alert);
|
||||
} catch (AlertNoticeException e) {
|
||||
log.warn("DispatchTask sendNoticeMsg error, message: {}", e.getMessage());
|
||||
}
|
||||
}));
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
+1
-3
@@ -28,9 +28,7 @@ public interface AlertStoreHandler {
|
||||
* Persistent alarm records
|
||||
* It is necessary to associate and assign values
|
||||
* to the alert tag information tags while persisting.
|
||||
*
|
||||
* @param alert alarm information
|
||||
* @return groupAlert
|
||||
*/
|
||||
GroupAlert store(GroupAlert alert);
|
||||
void store(GroupAlert alert);
|
||||
}
|
||||
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.alert.notice.impl;
|
||||
|
||||
import java.util.ResourceBundle;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.alert.notice.AlertNoticeException;
|
||||
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
|
||||
import org.apache.hertzbeat.common.util.ResourceBundleUtil;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Send alarm information through Alibaba Cloud SMS
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
@ConditionalOnProperty("common.sms.aliyun.app-id")
|
||||
final class AliYunAlertNotifyHandlerImpl extends AbstractAlertNotifyHandlerImpl {
|
||||
|
||||
private final ResourceBundle bundle = ResourceBundleUtil.getBundle("alerter");
|
||||
|
||||
@Override
|
||||
public void send(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) {
|
||||
// SMS notification
|
||||
try {
|
||||
// todo send aliyun sms
|
||||
} catch (Exception e) {
|
||||
throw new AlertNoticeException("[Sms Notify Error] " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte type() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
+14
-26
@@ -17,7 +17,6 @@
|
||||
|
||||
package org.apache.hertzbeat.alert.notice.impl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -47,53 +46,44 @@ final class DbAlertStoreHandlerImpl implements AlertStoreHandler {
|
||||
private final SingleAlertDao singleAlertDao;
|
||||
|
||||
@Override
|
||||
public GroupAlert store(GroupAlert groupAlert) {
|
||||
public void store(GroupAlert groupAlert) {
|
||||
if (groupAlert == null || groupAlert.getAlerts() == null || groupAlert.getAlerts().isEmpty()) {
|
||||
log.error("The Group Alerts is empty, ignore store");
|
||||
return groupAlert;
|
||||
return;
|
||||
}
|
||||
// 1. Find existing alert group
|
||||
GroupAlert existGroupAlert = groupAlertDao.findByGroupKey(groupAlert.getGroupKey());
|
||||
|
||||
// 2. Process individual alerts
|
||||
Set<String> alertFingerprints = new HashSet<>(8);
|
||||
|
||||
List<SingleAlert> originalAlerts = groupAlert.getAlerts();
|
||||
List<SingleAlert> newAlerts = new ArrayList<>();
|
||||
|
||||
|
||||
for (SingleAlert singleAlert : originalAlerts) {
|
||||
groupAlert.getAlerts().forEach(singleAlert -> {
|
||||
SingleAlert existAlert = singleAlertDao.findByFingerprint(singleAlert.getFingerprint());
|
||||
|
||||
if (existAlert != null) {
|
||||
// Update the existing alert with the ID and creation time from the database
|
||||
// Update existing alert
|
||||
singleAlert.setId(existAlert.getId());
|
||||
singleAlert.setGmtCreate(existAlert.getGmtCreate());
|
||||
|
||||
|
||||
// Status transition logic
|
||||
if (CommonConstants.ALERT_STATUS_FIRING.equals(singleAlert.getStatus())) {
|
||||
// If the alert is firing and the existing alert is not resolved, update the start time and trigger times
|
||||
if (!CommonConstants.ALERT_STATUS_RESOLVED.equals(existAlert.getStatus())) {
|
||||
singleAlert.setStartAt(existAlert.getStartAt());
|
||||
int triggerTimes = Optional.ofNullable(existAlert.getTriggerTimes()).orElse(1)
|
||||
+ Optional.ofNullable(singleAlert.getTriggerTimes()).orElse(1);
|
||||
int triggerTimes = Optional.ofNullable(existAlert.getTriggerTimes()).orElse(1) + Optional.ofNullable(singleAlert.getTriggerTimes()).orElse(1);
|
||||
singleAlert.setTriggerTimes(triggerTimes);
|
||||
}
|
||||
}
|
||||
} else if (CommonConstants.ALERT_STATUS_RESOLVED.equals(singleAlert.getStatus())) {
|
||||
// If the alert is resolved, set the end time (if not already set) and copy other fields from the existing alert
|
||||
// Transition to resolved state
|
||||
if (singleAlert.getEndAt() == null) {
|
||||
singleAlert.setEndAt(System.currentTimeMillis());
|
||||
singleAlert.setEndAt(System.currentTimeMillis());
|
||||
}
|
||||
singleAlert.setStartAt(existAlert.getStartAt());
|
||||
singleAlert.setActiveAt(existAlert.getActiveAt());
|
||||
singleAlert.setTriggerTimes(existAlert.getTriggerTimes());
|
||||
}
|
||||
}
|
||||
SingleAlert savedSingleAlert = singleAlertDao.save(singleAlert);
|
||||
newAlerts.add(savedSingleAlert);
|
||||
alertFingerprints.add(savedSingleAlert.getFingerprint());
|
||||
}
|
||||
groupAlert.setAlerts(newAlerts);
|
||||
alertFingerprints.add(singleAlert.getFingerprint());
|
||||
singleAlertDao.save(singleAlert);
|
||||
});
|
||||
|
||||
// 3. Process resolved alerts
|
||||
if (existGroupAlert != null) {
|
||||
List<String> existFingerprints = existGroupAlert.getAlertFingerprints();
|
||||
@@ -130,8 +120,6 @@ final class DbAlertStoreHandlerImpl implements AlertStoreHandler {
|
||||
|
||||
// 4. Save alert group
|
||||
groupAlert.setAlertFingerprints(alertFingerprints.stream().toList());
|
||||
GroupAlert savedGroupAlert = groupAlertDao.save(groupAlert);
|
||||
savedGroupAlert.setAlerts(groupAlert.getAlerts());
|
||||
return savedGroupAlert;
|
||||
groupAlertDao.save(groupAlert);
|
||||
}
|
||||
}
|
||||
|
||||
+23
-10
@@ -21,12 +21,12 @@ import java.util.ResourceBundle;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.alert.notice.AlertNoticeException;
|
||||
import org.apache.hertzbeat.alert.service.SmsClient;
|
||||
import org.apache.hertzbeat.alert.service.SmsClientFactory;
|
||||
import org.apache.hertzbeat.alert.service.TencentSmsClient;
|
||||
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
|
||||
import org.apache.hertzbeat.common.util.ResourceBundleUtil;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
@@ -35,22 +35,35 @@ import org.springframework.stereotype.Component;
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
@ConditionalOnProperty("common.sms.tencent.app-id")
|
||||
@Deprecated
|
||||
final class SmsAlertNotifyHandlerImpl extends AbstractAlertNotifyHandlerImpl {
|
||||
|
||||
private final SmsClientFactory smsFactory;
|
||||
private final TencentSmsClient tencentSmsClient;
|
||||
|
||||
private final ResourceBundle bundle = ResourceBundleUtil.getBundle("alerter");
|
||||
|
||||
@Override
|
||||
public void send(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) {
|
||||
// SMS notification todo use the rest api not sdk
|
||||
try {
|
||||
SmsClient smsClient = smsFactory.getSmsClient();
|
||||
if (smsClient == null) {
|
||||
throw new AlertNoticeException("No SMS Service available, please check the configuration");
|
||||
String instance = null;
|
||||
String priority = null;
|
||||
String content = null;
|
||||
if (alert.getCommonLabels() != null) {
|
||||
instance = alert.getCommonLabels().get("instance");
|
||||
priority = alert.getCommonLabels().get("priority");
|
||||
content = alert.getCommonAnnotations().get("summary");
|
||||
content = content == null ? alert.getCommonAnnotations().get("description") : content;
|
||||
if (content == null) {
|
||||
content = alert.getCommonAnnotations().values().stream().findFirst().orElse(null);
|
||||
}
|
||||
}
|
||||
if (!smsClient.checkConfig()) {
|
||||
throw new AlertNoticeException(smsClient.getType() + " SMS Service configuration is invalid, please check the configuration");
|
||||
}
|
||||
smsClient.sendMessage(receiver, noticeTemplate, alert);
|
||||
String[] params = new String[3];
|
||||
params[0] = instance == null ? alert.getGroupKey() : instance;
|
||||
params[1] = priority == null ? "unknown" : priority;
|
||||
params[2] = content;
|
||||
tencentSmsClient.sendMessage(params, new String[]{receiver.getPhone()});
|
||||
} catch (Exception e) {
|
||||
throw new AlertNoticeException("[Sms Notify Error] " + e.getMessage());
|
||||
}
|
||||
|
||||
-3
@@ -48,7 +48,6 @@ final class TelegramBotAlertNotifyHandlerImpl extends AbstractAlertNotifyHandler
|
||||
TelegramBotNotifyDTO notifyBody = TelegramBotNotifyDTO.builder()
|
||||
.chatId(receiver.getTgUserId())
|
||||
.text(renderContent(noticeTemplate, alert))
|
||||
.messageThreadId(receiver.getTgMessageThreadId())
|
||||
.disableWebPagePreview(true)
|
||||
.build();
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
@@ -85,8 +84,6 @@ final class TelegramBotAlertNotifyHandlerImpl extends AbstractAlertNotifyHandler
|
||||
private String text;
|
||||
@JsonProperty("disable_web_page_preview")
|
||||
private Boolean disableWebPagePreview;
|
||||
@JsonProperty("message_thread_id")
|
||||
private String messageThreadId;
|
||||
}
|
||||
|
||||
@NoArgsConstructor
|
||||
|
||||
+1
@@ -80,6 +80,7 @@ final class WeChatAlertNotifyHandlerImpl extends AbstractAlertNotifyHandlerImpl
|
||||
JsonObject textContent = new JsonObject();
|
||||
|
||||
// Here you can construct the message content based on the NoticeTemplate and Alert information
|
||||
// String alertMessage = String.format("警告:%s\n详情:%s", alert.getAlertDefineId(), alert.getContent());
|
||||
String alertMessage = "Alert message content";
|
||||
textContent.addProperty("content", alertMessage);
|
||||
messageContent.add("text", textContent);
|
||||
|
||||
+2
-2
@@ -21,8 +21,8 @@ package org.apache.hertzbeat.alert.reduce;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@@ -267,7 +267,7 @@ public class AlarmGroupReduce {
|
||||
.groupLabels(alert.getLabels())
|
||||
.commonLabels(alert.getLabels())
|
||||
.commonAnnotations(alert.getAnnotations())
|
||||
.alerts(new LinkedList<>(List.of(alert)))
|
||||
.alerts(Collections.singletonList(alert))
|
||||
.status(alert.getStatus())
|
||||
.build();
|
||||
|
||||
|
||||
+3
-3
@@ -26,10 +26,10 @@ import java.util.Map;
|
||||
public interface DataSourceService {
|
||||
|
||||
/**
|
||||
* execute query expr calculate
|
||||
* execute query
|
||||
* @param datasource datasource
|
||||
* @param expr query expr
|
||||
* @param query query
|
||||
* @return result
|
||||
*/
|
||||
List<Map<String, Object>> calculate(String datasource, String expr);
|
||||
List<Map<String, Object>> query(String datasource, String query);
|
||||
}
|
||||
|
||||
+4
-25
@@ -23,7 +23,6 @@ import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeRule;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
|
||||
import org.springframework.data.domain.Page;
|
||||
|
||||
/**
|
||||
* Message notification configuration interface
|
||||
@@ -33,32 +32,23 @@ public interface NoticeConfigService {
|
||||
/**
|
||||
* Dynamic conditional query
|
||||
* @param name Recipient name,support fuzzy query
|
||||
* @param pageIndex Page number
|
||||
* @param pageSize Number of records per page
|
||||
* @return Search result
|
||||
*/
|
||||
Page<NoticeReceiver> getNoticeReceivers(String name, int pageIndex, int pageSize);
|
||||
List<NoticeReceiver> getNoticeReceivers(String name);
|
||||
|
||||
/**
|
||||
* Dynamic conditional query
|
||||
* @param name Template name,support fuzzy query
|
||||
* @param preset Whether it is a system preset template
|
||||
* true: System preset template
|
||||
* false: Custom template
|
||||
* @param pageIndex Page number
|
||||
* @param pageSize Number of records per page
|
||||
* @return Search result
|
||||
*/
|
||||
Page<NoticeTemplate> getNoticeTemplates(String name, boolean preset, int pageIndex, int pageSize);
|
||||
List<NoticeTemplate> getNoticeTemplates(String name);
|
||||
|
||||
/**
|
||||
* Dynamic conditional query
|
||||
* @param name Recipient name ,support fuzzy query
|
||||
* @param pageIndex Page number
|
||||
* @param pageSize Number of records per page
|
||||
* @param name Recipient name
|
||||
* @return Search result
|
||||
*/
|
||||
Page<NoticeRule> getNoticeRules(String name, int pageIndex, int pageSize);
|
||||
List<NoticeRule> getNoticeRules(String name);
|
||||
|
||||
/**
|
||||
* Add a notification recipient
|
||||
@@ -164,15 +154,4 @@ public interface NoticeConfigService {
|
||||
*/
|
||||
boolean sendTestMsg(NoticeReceiver noticeReceiver);
|
||||
|
||||
/**
|
||||
* Query all notification recipients
|
||||
* @return Recipient List
|
||||
*/
|
||||
List<NoticeReceiver> getAllNoticeReceivers();
|
||||
|
||||
/**
|
||||
* Query all notification policies
|
||||
* @return Notification Policy List
|
||||
*/
|
||||
List<NoticeTemplate> getAllNoticeTemplates();
|
||||
}
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* 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.common.entity.alerter.GroupAlert;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
|
||||
|
||||
/**
|
||||
* SMS client interface
|
||||
*/
|
||||
public interface SmsClient {
|
||||
/**
|
||||
* send SMS
|
||||
*/
|
||||
void sendMessage(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert);
|
||||
|
||||
/**
|
||||
* get SMS provider type
|
||||
*/
|
||||
String getType();
|
||||
|
||||
/**
|
||||
* check SMS configuration, return true if the configuration is correct
|
||||
*/
|
||||
boolean checkConfig();
|
||||
}
|
||||
-154
@@ -1,154 +0,0 @@
|
||||
/*
|
||||
* 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.databind.ObjectMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.alert.config.SmsConfig;
|
||||
import org.apache.hertzbeat.alert.service.impl.SmsLocalSmsClientImpl;
|
||||
import org.apache.hertzbeat.alert.service.impl.AwsSmsClientImpl;
|
||||
import org.apache.hertzbeat.alert.service.impl.TencentSmsClientImpl;
|
||||
import org.apache.hertzbeat.alert.service.impl.TwilioSmsClientImpl;
|
||||
import org.apache.hertzbeat.alert.service.impl.UniSmsClientImpl;
|
||||
import org.apache.hertzbeat.alert.service.impl.AlibabaSmsClientImpl;
|
||||
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.support.event.SmsConfigChangeEvent;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import static org.apache.hertzbeat.common.constants.SmsConstants.ALIBABA;
|
||||
import static org.apache.hertzbeat.common.constants.SmsConstants.AWS;
|
||||
import static org.apache.hertzbeat.common.constants.SmsConstants.TENCENT;
|
||||
import static org.apache.hertzbeat.common.constants.SmsConstants.TWILIO;
|
||||
import static org.apache.hertzbeat.common.constants.SmsConstants.UNISMS;
|
||||
import static org.apache.hertzbeat.common.constants.SmsConstants.SMSLOCAL;
|
||||
|
||||
/**
|
||||
* SMS client factory
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class SmsClientFactory {
|
||||
|
||||
private static final String TYPE = GeneralConfigTypeEnum.sms.name();
|
||||
|
||||
private final GeneralConfigDao generalConfigDao;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final SmsConfig yamlSmsConfig;
|
||||
|
||||
private volatile SmsClient currentSmsClient;
|
||||
|
||||
public SmsClientFactory(GeneralConfigDao generalConfigDao, ObjectMapper objectMapper, SmsConfig yamlSmsConfig) {
|
||||
this.generalConfigDao = generalConfigDao;
|
||||
this.objectMapper = objectMapper;
|
||||
this.yamlSmsConfig = yamlSmsConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* SMS configuration change event listener
|
||||
*/
|
||||
@EventListener(SmsConfigChangeEvent.class)
|
||||
public void onSmsConfigChange(SmsConfigChangeEvent event) {
|
||||
log.info("[SmsClientFactory] SMS configuration change event received");
|
||||
synchronized (this) {
|
||||
currentSmsClient = null;
|
||||
}
|
||||
}
|
||||
|
||||
public SmsClient getSmsClient() {
|
||||
if (currentSmsClient != null) {
|
||||
return currentSmsClient;
|
||||
}
|
||||
synchronized (this) {
|
||||
if (currentSmsClient != null) {
|
||||
return currentSmsClient;
|
||||
}
|
||||
loadConfig();
|
||||
return currentSmsClient;
|
||||
}
|
||||
}
|
||||
|
||||
private void loadConfig() {
|
||||
try {
|
||||
// 1. try to load database configuration
|
||||
SmsConfig dbConfig = loadDatabaseConfig();
|
||||
if (dbConfig != null && !dbConfig.getType().isBlank() && dbConfig.isEnable()) {
|
||||
createSmsClient(dbConfig);
|
||||
if (currentSmsClient != null) {
|
||||
log.info("[SmsClientFactory] Using database SMS configuration, provider: {}", dbConfig.getType());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. try to load YAML configuration
|
||||
if (yamlSmsConfig != null && !yamlSmsConfig.getType().isBlank() && yamlSmsConfig.isEnable()) {
|
||||
createSmsClient(yamlSmsConfig);
|
||||
if (currentSmsClient != null) {
|
||||
log.info("[SmsClientFactory] Using YAML SMS configuration, provider: {}", yamlSmsConfig.getType());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
log.warn("[SmsClientFactory] No valid SMS configuration found");
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[SmsClientFactory] Failed to load SMS configuration", e);
|
||||
currentSmsClient = null;
|
||||
}
|
||||
}
|
||||
|
||||
private SmsConfig loadDatabaseConfig() {
|
||||
try {
|
||||
GeneralConfig config = generalConfigDao.findByType(TYPE);
|
||||
if (config != null && config.getContent() != null) {
|
||||
return objectMapper.readValue(config.getContent(), SmsConfig.class);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("[SmsClientFactory] Failed to load database configuration", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void createSmsClient(SmsConfig smsConfig) {
|
||||
switch (smsConfig.getType()) {
|
||||
case TENCENT:
|
||||
currentSmsClient = new TencentSmsClientImpl(smsConfig.getTencent());
|
||||
break;
|
||||
case UNISMS:
|
||||
currentSmsClient = new UniSmsClientImpl(smsConfig.getUnisms());
|
||||
break;
|
||||
case ALIBABA:
|
||||
currentSmsClient = new AlibabaSmsClientImpl(smsConfig.getAlibaba());
|
||||
break;
|
||||
case SMSLOCAL:
|
||||
currentSmsClient = new SmsLocalSmsClientImpl(smsConfig.getSmslocal());
|
||||
break;
|
||||
case AWS:
|
||||
currentSmsClient = new AwsSmsClientImpl(smsConfig.getAws());
|
||||
break;
|
||||
case TWILIO:
|
||||
currentSmsClient = new TwilioSmsClientImpl(smsConfig.getTwilio());
|
||||
break;
|
||||
default:
|
||||
log.warn("[SmsClientFactory] Unsupported SMS provider type: {}", smsConfig.getType());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* 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.tencentcloudapi.common.Credential;
|
||||
import com.tencentcloudapi.sms.v20210111.SmsClient;
|
||||
import com.tencentcloudapi.sms.v20210111.models.SendSmsRequest;
|
||||
import com.tencentcloudapi.sms.v20210111.models.SendSmsResponse;
|
||||
import com.tencentcloudapi.sms.v20210111.models.SendStatus;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.common.config.CommonProperties;
|
||||
import org.apache.hertzbeat.common.support.exception.SendMessageException;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* sms service client for tencent cloud
|
||||
*/
|
||||
@Component
|
||||
@ConditionalOnProperty("common.sms.tencent.app-id")
|
||||
@Slf4j
|
||||
public class TencentSmsClient {
|
||||
|
||||
private static final String RESPONSE_OK = "Ok";
|
||||
private static final String REGION = "ap-guangzhou";
|
||||
|
||||
private SmsClient smsClient;
|
||||
private String appId;
|
||||
private String signName;
|
||||
private String templateId;
|
||||
|
||||
public TencentSmsClient(CommonProperties properties) {
|
||||
if (properties == null || properties.getSms() == null || properties.getSms().getTencent() == null) {
|
||||
log.error("init error, please config TencentSmsClient props in application.yml");
|
||||
throw new IllegalArgumentException("please config TencentSmsClient props");
|
||||
}
|
||||
initSmsClient(properties.getSms().getTencent());
|
||||
}
|
||||
|
||||
private void initSmsClient(CommonProperties.TencentSmsProperties tencent) {
|
||||
this.appId = tencent.getAppId();
|
||||
this.signName = tencent.getSignName();
|
||||
this.templateId = tencent.getTemplateId();
|
||||
Credential cred = new Credential(tencent.getSecretId(), tencent.getSecretKey());
|
||||
smsClient = new SmsClient(cred, REGION);
|
||||
}
|
||||
|
||||
/**
|
||||
* send text message
|
||||
* @param appId appId
|
||||
* @param signName sign name
|
||||
* @param templateId template id
|
||||
* @param templateValues template values
|
||||
* @param phones phones num
|
||||
*/
|
||||
public void sendMessage(String appId, String signName, String templateId,
|
||||
String[] templateValues, String[] phones) {
|
||||
SendSmsRequest req = new SendSmsRequest();
|
||||
req.setSmsSdkAppId(appId);
|
||||
req.setSignName(signName);
|
||||
req.setTemplateId(templateId);
|
||||
req.setTemplateParamSet(templateValues);
|
||||
req.setPhoneNumberSet(phones);
|
||||
try {
|
||||
SendSmsResponse smsResponse = this.smsClient.SendSms(req);
|
||||
SendStatus sendStatus = smsResponse.getSendStatusSet()[0];
|
||||
if (!RESPONSE_OK.equals(sendStatus.getCode())) {
|
||||
throw new SendMessageException(sendStatus.getCode() + ":" + sendStatus.getMessage());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn(e.getMessage());
|
||||
throw new SendMessageException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* send text message
|
||||
* @param templateValues template values
|
||||
* @param phones phones num
|
||||
*/
|
||||
public void sendMessage(String[] templateValues, String[] phones) {
|
||||
sendMessage(this.appId, this.signName, this.templateId, templateValues, phones);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+1
-11
@@ -25,7 +25,6 @@ import jakarta.persistence.criteria.CriteriaBuilder;
|
||||
import jakarta.persistence.criteria.Predicate;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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;
|
||||
@@ -68,9 +67,6 @@ public class AlertDefineServiceImpl implements AlertDefineService {
|
||||
|
||||
@Autowired
|
||||
private AlertDefineDao alertDefineDao;
|
||||
|
||||
@Autowired
|
||||
private PeriodicAlertRuleScheduler periodicAlertRuleScheduler;
|
||||
|
||||
private final Map<String, AlertDefineImExportService> alertDefineImExportServiceMap = new HashMap<>();
|
||||
|
||||
@@ -102,22 +98,19 @@ public class AlertDefineServiceImpl implements AlertDefineService {
|
||||
|
||||
@Override
|
||||
public void addAlertDefine(AlertDefine alertDefine) throws RuntimeException {
|
||||
alertDefine = alertDefineDao.save(alertDefine);
|
||||
periodicAlertRuleScheduler.updateSchedule(alertDefine);
|
||||
alertDefineDao.save(alertDefine);
|
||||
CacheFactory.clearAlertDefineCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void modifyAlertDefine(AlertDefine alertDefine) throws RuntimeException {
|
||||
alertDefineDao.save(alertDefine);
|
||||
periodicAlertRuleScheduler.updateSchedule(alertDefine);
|
||||
CacheFactory.clearAlertDefineCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteAlertDefine(long alertId) throws RuntimeException {
|
||||
alertDefineDao.deleteById(alertId);
|
||||
periodicAlertRuleScheduler.cancelSchedule(alertId);
|
||||
CacheFactory.clearAlertDefineCache();
|
||||
}
|
||||
|
||||
@@ -130,9 +123,6 @@ public class AlertDefineServiceImpl implements AlertDefineService {
|
||||
@Override
|
||||
public void deleteAlertDefines(Set<Long> alertIds) throws RuntimeException {
|
||||
alertDefineDao.deleteAlertDefinesByIdIn(alertIds);
|
||||
for (Long alertId : alertIds) {
|
||||
periodicAlertRuleScheduler.cancelSchedule(alertId);
|
||||
}
|
||||
CacheFactory.clearAlertDefineCache();
|
||||
}
|
||||
|
||||
|
||||
-234
@@ -1,234 +0,0 @@
|
||||
/*
|
||||
* 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.hertzbeat.alert.config.AlibabaSmsProperties;
|
||||
import org.apache.hertzbeat.alert.service.SmsClient;
|
||||
import org.apache.hertzbeat.alert.util.CryptoUtils;
|
||||
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
|
||||
import org.apache.hertzbeat.common.support.exception.SendMessageException;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.SimpleTimeZone;
|
||||
import java.util.TreeMap;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.apache.hertzbeat.common.constants.SmsConstants.ALIBABA;
|
||||
|
||||
/**
|
||||
* Alibaba Cloud SMS Client Implementation<br>
|
||||
* API doc: <a href="https://next.api.aliyun.com/document/Dysmsapi/2017-05-25/SendSms">https://next.api.aliyun.com/document/Dysmsapi/2017-05-25/SendSms</a><br>
|
||||
* Singnature doc: <a href="https://help.aliyun.com/zh/sdk/product-overview/v3-request-structure-and-signature">https://help.aliyun.com/zh/sdk/product-overview/v3-request-structure-and-signature</a>
|
||||
*/
|
||||
@Slf4j
|
||||
public class AlibabaSmsClientImpl implements SmsClient {
|
||||
|
||||
private static final String API_VERSION = "2017-05-25";
|
||||
private static final String ACTION = "SendSms";
|
||||
private static final String HOST = "dysmsapi.aliyuncs.com";
|
||||
private static final String ALGORITHM = "ACS3-HMAC-SHA256";
|
||||
|
||||
private final String accessKeyId;
|
||||
private final String accessKeySecret;
|
||||
private final String signName;
|
||||
private final String templateCode;
|
||||
|
||||
public AlibabaSmsClientImpl(AlibabaSmsProperties config) {
|
||||
if (config != null) {
|
||||
this.accessKeyId = config.getAccessKeyId();
|
||||
this.accessKeySecret = config.getAccessKeySecret();
|
||||
this.signName = config.getSignName();
|
||||
this.templateCode = config.getTemplateCode();
|
||||
} else {
|
||||
this.accessKeyId = "";
|
||||
this.accessKeySecret = "";
|
||||
this.signName = "";
|
||||
this.templateCode = "";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) {
|
||||
// Extract alert info
|
||||
String instance = null;
|
||||
String priority = null;
|
||||
String content = null;
|
||||
if (alert.getCommonLabels() != null) {
|
||||
instance = alert.getCommonLabels().get("instance");
|
||||
priority = alert.getCommonLabels().get("priority");
|
||||
content = alert.getCommonAnnotations().get("summary");
|
||||
content = content == null ? alert.getCommonAnnotations().get("description") : content;
|
||||
if (content == null) {
|
||||
content = alert.getCommonAnnotations().values().stream().findFirst().orElse(null);
|
||||
}
|
||||
}
|
||||
|
||||
// Build template parameters
|
||||
Map<String, String> templateParam = new HashMap<>();
|
||||
templateParam.put("instance", instance == null ? alert.getGroupKey() : instance);
|
||||
templateParam.put("priority", priority == null ? "unknown" : priority);
|
||||
templateParam.put("content", content);
|
||||
|
||||
sendSms(receiver.getPhone(), JsonUtil.toJson(templateParam));
|
||||
}
|
||||
|
||||
private void sendSms(String phoneNumber, String templateParam) {
|
||||
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
|
||||
// Build query parameters
|
||||
Map<String, String> queryParams = new TreeMap<>();
|
||||
queryParams.put("PhoneNumbers", phoneNumber);
|
||||
queryParams.put("SignName", signName);
|
||||
queryParams.put("TemplateCode", templateCode);
|
||||
queryParams.put("TemplateParam", templateParam);
|
||||
|
||||
// Build canonical query string
|
||||
StringBuilder canonicalQueryString = new StringBuilder();
|
||||
queryParams.forEach((key, value) -> {
|
||||
if (canonicalQueryString.length() > 0) {
|
||||
canonicalQueryString.append("&");
|
||||
}
|
||||
canonicalQueryString.append(percentEncode(key))
|
||||
.append("=")
|
||||
.append(percentEncode(value));
|
||||
});
|
||||
|
||||
// Generate timestamp and nonce
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
|
||||
sdf.setTimeZone(new SimpleTimeZone(0, "GMT"));
|
||||
String timestamp = sdf.format(new Date());
|
||||
String nonce = UUID.randomUUID().toString();
|
||||
|
||||
// Calculate signature
|
||||
String authorization = calculateAuthorization(
|
||||
canonicalQueryString.toString(),
|
||||
timestamp,
|
||||
nonce
|
||||
);
|
||||
|
||||
// Build URL
|
||||
String url = "https://" + HOST + "/?" + canonicalQueryString;
|
||||
|
||||
// Build HTTP request
|
||||
HttpPost httpPost = new HttpPost(url);
|
||||
httpPost.setHeader("Content-Type", "application/json");
|
||||
httpPost.setHeader("Host", HOST);
|
||||
httpPost.setHeader("Authorization", authorization);
|
||||
httpPost.setHeader("x-acs-action", ACTION);
|
||||
httpPost.setHeader("x-acs-version", API_VERSION);
|
||||
httpPost.setHeader("x-acs-date", timestamp);
|
||||
httpPost.setHeader("x-acs-signature-nonce", nonce);
|
||||
httpPost.setHeader("x-acs-content-sha256",
|
||||
CryptoUtils.sha256Hex(""));
|
||||
|
||||
log.info("Sending Alibaba SMS request to {}", url + ", params: " + templateParam + "headers: " + Arrays.toString(httpPost.getAllHeaders()));
|
||||
|
||||
// Send request and handle response
|
||||
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
String responseBody = EntityUtils.toString(response.getEntity());
|
||||
|
||||
log.info("SMS response status: {}, body: {}", statusCode, responseBody);
|
||||
|
||||
if (statusCode != 200) {
|
||||
throw new SendMessageException("HTTP request failed with status code: " + statusCode + ", response: " + responseBody);
|
||||
}
|
||||
|
||||
JsonNode jsonResponse = JsonUtil.fromJson(responseBody);
|
||||
String code = jsonResponse.get("Code").asText();
|
||||
if (!"OK".equals(code)) {
|
||||
String message = jsonResponse.get("Message").asText();
|
||||
throw new SendMessageException(code + ":" + message);
|
||||
}
|
||||
|
||||
log.info("Successfully sent SMS to phone: {}", phoneNumber);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to send SMS: {}", e.getMessage());
|
||||
throw new SendMessageException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private String calculateAuthorization(String canonicalQueryString, String timestamp, String nonce) {
|
||||
try {
|
||||
// Step 1: Build canonical request
|
||||
String canonicalRequest = buildCanonicalRequest(canonicalQueryString, timestamp, nonce);
|
||||
|
||||
// Step 2: Build string to sign
|
||||
String stringToSign = ALGORITHM + "\n" + CryptoUtils.sha256Hex(canonicalRequest);
|
||||
|
||||
// Step 3: Calculate signature
|
||||
String signature = CryptoUtils.hmacSha256Hex(accessKeySecret, stringToSign);
|
||||
|
||||
// Step 4: Build authorization header
|
||||
return ALGORITHM + " Credential=" + accessKeyId + ",SignedHeaders=host;x-acs-action;x-acs-content-sha256;x-acs-date;" + "x-acs-signature-nonce;x-acs-version,Signature=" + signature;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to calculate authorization", e);
|
||||
}
|
||||
}
|
||||
|
||||
private String buildCanonicalRequest(String canonicalQueryString, String timestamp, String nonce) {
|
||||
return "POST\n"
|
||||
+ "/\n"
|
||||
+ canonicalQueryString + "\n"
|
||||
+ "host:" + HOST + "\n"
|
||||
+ "x-acs-action:" + ACTION + "\n"
|
||||
+ "x-acs-content-sha256:" + CryptoUtils.sha256Hex("") + "\n"
|
||||
+ "x-acs-date:" + timestamp + "\n"
|
||||
+ "x-acs-signature-nonce:" + nonce + "\n"
|
||||
+ "x-acs-version:" + API_VERSION + "\n\n"
|
||||
+ "host;x-acs-action;x-acs-content-sha256;x-acs-date;"
|
||||
+ "x-acs-signature-nonce;x-acs-version\n"
|
||||
+ CryptoUtils.sha256Hex("");
|
||||
}
|
||||
|
||||
private String percentEncode(String value) {
|
||||
try {
|
||||
return java.net.URLEncoder.encode(value, StandardCharsets.UTF_8)
|
||||
.replace("+", "%20")
|
||||
.replace("*", "%2A")
|
||||
.replace("%7E", "~");
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to encode value", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return ALIBABA;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkConfig() {
|
||||
return !(accessKeyId.isBlank() || accessKeySecret.isBlank() || signName.isBlank() || templateCode.isBlank());
|
||||
}
|
||||
}
|
||||
-287
@@ -1,287 +0,0 @@
|
||||
/*
|
||||
* 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.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.alert.config.AwsSmsProperties;
|
||||
import org.apache.hertzbeat.alert.service.SmsClient;
|
||||
import org.apache.hertzbeat.alert.util.CryptoUtils;
|
||||
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
|
||||
import org.apache.hertzbeat.common.support.exception.SendMessageException;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.entity.ContentType;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.apache.hertzbeat.common.constants.SmsConstants.AWS;
|
||||
|
||||
/**
|
||||
* AWS Cloud SMS Client Implementation<br>
|
||||
* API doc:<a href="https://docs.aws.amazon.com/pinpoint/latest/apireference_smsvoicev2/API_SendTextMessage.html">https://docs.aws.amazon.com/pinpoint/latest/apireference_smsvoicev2/API_SendTextMessage.html</a><br>
|
||||
* Signature doc:<a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_sigv-create-signed-request.html"</a>https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_sigv-create-signed-request.html</a>
|
||||
*/
|
||||
@Slf4j
|
||||
public class AwsSmsClientImpl implements SmsClient {
|
||||
|
||||
|
||||
private static final String SERVICE = "sms-voice";
|
||||
private static final String MESSAGE_TEMPLATE = "Instance: %s, Priority: %s, Content: %s";
|
||||
|
||||
private final String accessKey;
|
||||
private final String secretKey;
|
||||
private final String region;
|
||||
|
||||
public AwsSmsClientImpl(AwsSmsProperties config) {
|
||||
if (config != null) {
|
||||
this.accessKey = config.getAccessKeyId();
|
||||
this.secretKey = config.getAccessKeySecret();
|
||||
this.region = config.getRegion();
|
||||
} else {
|
||||
this.accessKey = "";
|
||||
this.secretKey = "";
|
||||
this.region = "";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) {
|
||||
// Extract alert info
|
||||
String instance = null;
|
||||
String priority = null;
|
||||
String content = null;
|
||||
if (alert.getCommonLabels() != null) {
|
||||
instance = alert.getCommonLabels().get("instance") == null ? alert.getGroupKey() : alert.getCommonLabels().get("instance");
|
||||
priority = alert.getCommonLabels().get("priority") == null ? "unknown" : alert.getCommonLabels().get("priority");
|
||||
content = alert.getCommonAnnotations().get("summary");
|
||||
content = content == null ? alert.getCommonAnnotations().get("description") : content;
|
||||
if (content == null) {
|
||||
content = alert.getCommonAnnotations().values().stream().findFirst().orElse(null);
|
||||
}
|
||||
}
|
||||
this.send(receiver.getPhone(), createMessage(instance, priority, content));
|
||||
}
|
||||
|
||||
private String createMessage(String instance, String priority, String content) {
|
||||
return String.format(MESSAGE_TEMPLATE, instance, priority, content);
|
||||
}
|
||||
|
||||
private void send(String phoneNumber, String message) {
|
||||
Map<String, Object> payload = new HashMap<>();
|
||||
payload.put("MessageBody", message);
|
||||
payload.put("DestinationPhoneNumber", phoneNumber);
|
||||
payload.put("MessageType", "TRANSACTIONAL");
|
||||
|
||||
Instant now = Instant.now();
|
||||
String amzDate = DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss'Z'").withZone(ZoneOffset.UTC).format(now);
|
||||
String endpoint = "https://" + SERVICE + "." + region + ".amazonaws.com";
|
||||
|
||||
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
|
||||
String payloadInString = new ObjectMapper().writeValueAsString(payload);
|
||||
URI requestUri = new URI(endpoint);
|
||||
|
||||
HttpPost httpPost = createHttpPost(requestUri, amzDate, payloadInString);
|
||||
log.info("Sending AWS SMS request to {}", requestUri + "," + "headers: " + Arrays.toString(httpPost.getAllHeaders()));
|
||||
executeRequest(httpClient, httpPost, phoneNumber);
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to send SMS: {}", e.getMessage());
|
||||
throw new SendMessageException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private HttpPost createHttpPost(URI requestUri, String amzDate, String payloadInString) throws Exception {
|
||||
// Define headers once to avoid repetition (Order of headers must be maintained)
|
||||
Map<String, String> headers = new LinkedHashMap<>();
|
||||
headers.put("content-type", "application/x-amz-json-1.0");
|
||||
headers.put("host", SERVICE + "." + this.region + ".amazonaws.com");
|
||||
headers.put("x-amz-date", amzDate);
|
||||
headers.put("x-amz-target", "PinpointSMSVoiceV2.SendTextMessage");
|
||||
|
||||
// Build authorization header using the same headers
|
||||
AwsAuthenticationBuilder authBuilder = new AwsAuthenticationBuilder()
|
||||
.credential(this.accessKey, this.secretKey)
|
||||
.region(this.region);
|
||||
headers.forEach(authBuilder::header);
|
||||
String authorizationHeader = authBuilder.build(requestUri, "POST", SERVICE, payloadInString);
|
||||
|
||||
// Create HTTP post and set all headers
|
||||
HttpPost httpPost = new HttpPost(requestUri);
|
||||
httpPost.setHeader("Authorization", authorizationHeader);
|
||||
headers.forEach(httpPost::setHeader);
|
||||
|
||||
httpPost.setEntity(new StringEntity(payloadInString, ContentType.APPLICATION_JSON));
|
||||
return httpPost;
|
||||
}
|
||||
|
||||
private void executeRequest(CloseableHttpClient httpClient, HttpPost httpPost, String phoneNumber) throws Exception {
|
||||
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
String responseBody = EntityUtils.toString(response.getEntity());
|
||||
log.info("SMS response status: {}, body: {}", statusCode, responseBody);
|
||||
|
||||
if (statusCode != 200) {
|
||||
throw new SendMessageException("HTTP request failed with status code: " + statusCode + ", response: " + responseBody);
|
||||
}
|
||||
|
||||
JsonNode jsonResponse = JsonUtil.fromJson(responseBody);
|
||||
if (jsonResponse == null) {
|
||||
throw new SendMessageException(statusCode + ":" + responseBody);
|
||||
}
|
||||
|
||||
JsonNode responseNode = jsonResponse.get("MessageId");
|
||||
if (responseNode == null) {
|
||||
throw new SendMessageException(statusCode + ":" + responseBody);
|
||||
}
|
||||
|
||||
String messageId = responseNode.asText();
|
||||
log.info("Successfully sent SMS to phone: {}, messageId: {}", phoneNumber, messageId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return AWS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkConfig() {
|
||||
return !(accessKey.isBlank() || secretKey.isBlank() || region.isBlank());
|
||||
}
|
||||
|
||||
|
||||
private static class AwsAuthenticationBuilder {
|
||||
private String accessKey;
|
||||
private String secretKey;
|
||||
private String region;
|
||||
private String service;
|
||||
private URI requestUri;
|
||||
private String method;
|
||||
private String payload;
|
||||
private final Map<String, String> headers = new LinkedHashMap<>();
|
||||
|
||||
public AwsAuthenticationBuilder region(String region) {
|
||||
this.region = region;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AwsAuthenticationBuilder credential(String accessKey, String secretKey) {
|
||||
this.accessKey = accessKey;
|
||||
this.secretKey = secretKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AwsAuthenticationBuilder header(String header, String value) {
|
||||
this.headers.put(header, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public String build(URI requestUri, String method, String service, String payload) throws Exception {
|
||||
this.requestUri = requestUri;
|
||||
this.method = method;
|
||||
this.service = service;
|
||||
this.payload = payload;
|
||||
return "%s Credential=%s/%s, SignedHeaders=%s, Signature=%s".formatted(
|
||||
getAlgorithm(),
|
||||
this.accessKey,
|
||||
getCredentialScope(),
|
||||
getSignedHeaders(),
|
||||
getSignature()
|
||||
);
|
||||
}
|
||||
|
||||
private String getCredentialScope() {
|
||||
return getDateStamp() + "/" + region + "/" + service + "/aws4_request";
|
||||
}
|
||||
|
||||
private String getSignedHeaders() {
|
||||
return String.join(";", headers.keySet());
|
||||
}
|
||||
|
||||
private String getDateTime() {
|
||||
return headers.get("x-amz-date");
|
||||
}
|
||||
|
||||
private String getDateStamp() {
|
||||
return getDateTime().substring(0, 8);
|
||||
}
|
||||
|
||||
private String getSignature() {
|
||||
String canonicalRequest = getCanonicalRequest();
|
||||
String stringToSign = String.join("\n",
|
||||
getAlgorithm(),
|
||||
getDateTime(),
|
||||
getCredentialScope(),
|
||||
CryptoUtils.sha256Hex(canonicalRequest));
|
||||
|
||||
byte[] signingKey = getSignatureKey(secretKey, getDateStamp(), region, service);
|
||||
return CryptoUtils.hmacSha256Hex(signingKey, stringToSign);
|
||||
}
|
||||
|
||||
private String getCanonicalRequest() {
|
||||
return String.join("\n",
|
||||
method,
|
||||
requestUri.getPath().isEmpty() ? "/" : requestUri.getPath(),
|
||||
requestUri.getQuery() != null ? requestUri.getQuery() : "",
|
||||
getCanonicalHeaders(),
|
||||
getSignedHeaders(),
|
||||
CryptoUtils.sha256Hex(payload)
|
||||
);
|
||||
}
|
||||
|
||||
private String getCanonicalHeaders() {
|
||||
return headers.entrySet().stream()
|
||||
.map(entry -> entry.getKey() + ":" + entry.getValue())
|
||||
.collect(Collectors.joining("\n")) + "\n";
|
||||
}
|
||||
|
||||
private String getAlgorithm() {
|
||||
return "AWS4-HMAC-SHA256";
|
||||
}
|
||||
|
||||
private byte[] getSignatureKey(String key, String dateStamp, String regionName, String serviceName) {
|
||||
byte[] secret = ("AWS4" + key).getBytes(StandardCharsets.UTF_8);
|
||||
byte[] date = CryptoUtils.hmac256(secret, dateStamp);
|
||||
byte[] region = CryptoUtils.hmac256(date, regionName);
|
||||
byte[] service = CryptoUtils.hmac256(region, serviceName);
|
||||
return CryptoUtils.hmac256(service, "aws4_request");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+9
-303
@@ -17,21 +17,13 @@
|
||||
|
||||
package org.apache.hertzbeat.alert.service.impl;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Stack;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.alert.service.DataSourceService;
|
||||
import org.apache.hertzbeat.warehouse.db.QueryExecutor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.ArrayList;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* datasource service
|
||||
@@ -41,307 +33,21 @@ import org.springframework.util.StringUtils;
|
||||
public class DataSourceServiceImpl implements DataSourceService {
|
||||
|
||||
@Autowired(required = false)
|
||||
private List<QueryExecutor> executors;
|
||||
|
||||
private static final Pattern EXPR_TOKEN = Pattern.compile("\\(|\\)|[a-zA-Z_][a-zA-Z0-9_=~{}\\[\\]\".]*|\\d+(\\.\\d+)?|>=|<=|==|!=|>|<|and|or|unless");
|
||||
private static final String THRESHOLD = "__threshold__";
|
||||
private static final String VALUE = "__value__";
|
||||
private Map<String, QueryExecutor> executors;
|
||||
|
||||
@Override
|
||||
public List<Map<String, Object>> calculate(String datasource, String expr) {
|
||||
if (!StringUtils.hasText(expr)) {
|
||||
throw new IllegalArgumentException("Empty expression");
|
||||
}
|
||||
if (executors == null || executors.isEmpty()) {
|
||||
throw new IllegalArgumentException("No query executor found");
|
||||
}
|
||||
QueryExecutor executor = executors.stream().filter(e -> e.support(datasource)).findFirst().orElse(null);
|
||||
public List<Map<String, Object>> query(String datasource, String query) {
|
||||
QueryExecutor executor = executors.get(datasource);
|
||||
if (executor == null) {
|
||||
throw new IllegalArgumentException("Unsupported datasource: " + datasource);
|
||||
}
|
||||
// replace all white space
|
||||
expr = expr.replaceAll("\\s+", " ");
|
||||
try {
|
||||
return evaluate(expr, executor);
|
||||
} catch (Exception e) {
|
||||
log.error("Error executing query on datasource {}: {}", datasource, e.getMessage());
|
||||
throw new RuntimeException("Query execution failed", e);
|
||||
}
|
||||
return executor.execute(query);
|
||||
}
|
||||
|
||||
private List<Map<String, Object>> evaluate(String expr, QueryExecutor executor) {
|
||||
Stack<List<Map<String, Object>>> values = new Stack<>();
|
||||
Stack<String> operators = new Stack<>();
|
||||
Matcher matcher = EXPR_TOKEN.matcher(expr);
|
||||
List<String> tokens = new ArrayList<>();
|
||||
while (matcher.find()) {
|
||||
tokens.add(matcher.group());
|
||||
}
|
||||
for (String token : tokens) {
|
||||
if (token.equals("(")) {
|
||||
operators.push(token);
|
||||
} else if (token.equals(")")) {
|
||||
while (!operators.isEmpty() && !operators.peek().equals("(")) {
|
||||
applyOperator(values, operators.pop());
|
||||
}
|
||||
// remove the left parenthesis
|
||||
operators.pop();
|
||||
} else if (token.matches(">=|<=|==|!=|>|<")) {
|
||||
operators.push(token);
|
||||
} else if (token.equals("and") || token.equals("or") || token.equals("unless")) {
|
||||
while (!operators.isEmpty() && precedence(operators.peek()) >= precedence(token)) {
|
||||
applyOperator(values, operators.pop());
|
||||
}
|
||||
operators.push(token);
|
||||
} else if (token.matches("\\d+(\\.\\d+)?")) {
|
||||
double value = Double.parseDouble(token);
|
||||
List<Map<String, Object>> numAsList = new ArrayList<>();
|
||||
numAsList.add(Map.of(THRESHOLD, value));
|
||||
values.push(numAsList);
|
||||
} else if (token.matches("[a-zA-Z_][a-zA-Z0-9_=~{}\\[\\]\".]*")) {
|
||||
List<Map<String, Object>> results = executor.execute(token);
|
||||
values.push(results);
|
||||
}
|
||||
}
|
||||
while (!operators.isEmpty()) {
|
||||
applyOperator(values, operators.pop());
|
||||
}
|
||||
return values.isEmpty() ? new LinkedList<>() : values.pop();
|
||||
}
|
||||
|
||||
private int precedence(String op) {
|
||||
return switch (op) {
|
||||
case "or" -> 1;
|
||||
case "unless" -> 2;
|
||||
case "and" -> 3;
|
||||
case ">", "<", ">=", "<=", "==", "!=" -> 4;
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
|
||||
private void applyOperator(Stack<List<Map<String, Object>>> values, String op) {
|
||||
if (values.size() < 2) {
|
||||
return;
|
||||
};
|
||||
List<Map<String, Object>> rightOperand = values.pop();
|
||||
List<Map<String, Object>> leftOperand = values.pop();
|
||||
if (rightOperand.size() == 1 && rightOperand.get(0).containsKey(THRESHOLD)) {
|
||||
double threshold = (double) rightOperand.get(0).get(THRESHOLD);
|
||||
List<Map<String, Object>> result = new ArrayList<>();
|
||||
for (Map<String, Object> item : leftOperand) {
|
||||
Object queryValues = item.get(VALUE);
|
||||
if (queryValues == null) {
|
||||
// ignore the query result data is empty
|
||||
continue;
|
||||
}
|
||||
// queryValues may be a list of values, or a single value
|
||||
Object matchValue = evaluateCondition(queryValues, op, threshold);
|
||||
item.put(VALUE, matchValue);
|
||||
// if matchValue is null, mean not match the threshold
|
||||
// if not null, mean match the threshold
|
||||
result.add(new HashMap<>(item));
|
||||
}
|
||||
if (!result.isEmpty()) {
|
||||
values.push(result);
|
||||
}
|
||||
return;
|
||||
}
|
||||
Map<String, Object> leftMap = null;
|
||||
boolean leftMatch = false;
|
||||
Map<String, Object> rightMap = null;
|
||||
boolean rightMatch = false;
|
||||
switch (op) {
|
||||
case "and" -> {
|
||||
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) {
|
||||
rightMap.putAll(leftMap);
|
||||
values.push(new LinkedList<>(List.of(rightMap)));
|
||||
} else if (leftMap != null) {
|
||||
leftMap.put(VALUE, null);
|
||||
values.push(new LinkedList<>(List.of(leftMap)));
|
||||
} else if (rightMap != null) {
|
||||
rightMap.put(VALUE, null);
|
||||
values.push(new LinkedList<>(List.of(rightMap)));
|
||||
}
|
||||
}
|
||||
case "or" -> {
|
||||
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) {
|
||||
rightMap.putAll(leftMap);
|
||||
values.push(new LinkedList<>(List.of(rightMap)));
|
||||
} else if (leftMatch) {
|
||||
values.push(new LinkedList<>(List.of(leftMap)));
|
||||
} else if (rightMatch) {
|
||||
values.push(new LinkedList<>(List.of(rightMap)));
|
||||
} else {
|
||||
if (leftMap != null && rightMap != null) {
|
||||
rightMap.putAll(leftMap);
|
||||
values.push(new LinkedList<>(List.of(rightMap)));
|
||||
} else if (leftMap != null) {
|
||||
values.push(new LinkedList<>(List.of(leftMap)));
|
||||
} else if (rightMap != null){
|
||||
values.push(new LinkedList<>(List.of(rightMap)));
|
||||
}
|
||||
}
|
||||
}
|
||||
case "unless" -> {
|
||||
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) {
|
||||
values.push(new LinkedList<>(List.of(leftMap)));
|
||||
} else {
|
||||
if (leftMap != null) {
|
||||
leftMap.put(VALUE, null);
|
||||
values.push(new LinkedList<>(List.of(leftMap)));
|
||||
} else {
|
||||
if (rightMap != null) {
|
||||
rightMap.put(VALUE, null);
|
||||
values.push(new LinkedList<>(List.of(rightMap)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
default -> throw new IllegalArgumentException("Unsupported operator: " + op);
|
||||
}
|
||||
}
|
||||
|
||||
private Object evaluateCondition(Object value, String operator, Double threshold) {
|
||||
// value may be a list of values, or a single value
|
||||
switch (operator) {
|
||||
case ">":
|
||||
// if value is list, return the max value
|
||||
if (value instanceof List<?> values) {
|
||||
Double doubleValue = values.stream().map(v -> Double.valueOf(v.toString()))
|
||||
.max(Double::compareTo).orElse(null);
|
||||
if (doubleValue != null) {
|
||||
return doubleValue > threshold ? doubleValue : null;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return Double.parseDouble(value.toString()) > threshold ? value : null;
|
||||
}
|
||||
case ">=":
|
||||
if (value instanceof List<?> values) {
|
||||
Double doubleValue = values.stream().map(v -> Double.valueOf(v.toString()))
|
||||
.max(Double::compareTo).orElse(null);
|
||||
if (doubleValue != null) {
|
||||
return doubleValue >= threshold ? doubleValue : null;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return Double.parseDouble(value.toString()) >= threshold ? value : null;
|
||||
}
|
||||
case "<":
|
||||
if (value instanceof List<?> values) {
|
||||
Double doubleValue = values.stream().map(v -> Double.valueOf(v.toString()))
|
||||
.min(Double::compareTo).orElse(null);
|
||||
if (doubleValue != null) {
|
||||
return doubleValue < threshold ? doubleValue : null;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return Double.parseDouble(value.toString()) < threshold ? value : null;
|
||||
}
|
||||
case "<=":
|
||||
if (value instanceof List<?> values) {
|
||||
Double doubleValue = values.stream().map(v -> Double.valueOf(v.toString()))
|
||||
.min(Double::compareTo).orElse(null);
|
||||
if (doubleValue != null) {
|
||||
return doubleValue <= threshold ? doubleValue : null;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return Double.parseDouble(value.toString()) <= threshold ? value : null;
|
||||
}
|
||||
case "==":
|
||||
if (value instanceof List<?> values) {
|
||||
for (Object v : values) {
|
||||
if (v.equals(threshold)) {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
return value.equals(threshold) ? value : null;
|
||||
}
|
||||
case "!=":
|
||||
if (value instanceof List<?> values) {
|
||||
for (Object v : values) {
|
||||
if (v.equals(threshold)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
} else {
|
||||
return value.equals(threshold) ? null : value;
|
||||
}
|
||||
default:
|
||||
// unsupported operator todo add more operator
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void setExecutors(List<QueryExecutor> mockExecutor) {
|
||||
this.executors = mockExecutor;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public interface QueryExecutor {
|
||||
List<Map<String, Object>> execute(String query);
|
||||
}
|
||||
}
|
||||
|
||||
+28
-77
@@ -18,6 +18,18 @@
|
||||
package org.apache.hertzbeat.alert.service.impl;
|
||||
|
||||
import jakarta.persistence.criteria.Predicate;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.hertzbeat.common.cache.CacheFactory;
|
||||
@@ -39,28 +51,10 @@ import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Message notification configuration implementation
|
||||
*/
|
||||
@@ -86,87 +80,44 @@ public class NoticeConfigServiceImpl implements NoticeConfigService, CommandLine
|
||||
private AlertNoticeDispatch dispatcherAlarm;
|
||||
|
||||
@Override
|
||||
public Page<NoticeReceiver> getNoticeReceivers(String name, int pageIndex, int pageSize) {
|
||||
public List<NoticeReceiver> getNoticeReceivers(String name) {
|
||||
Specification<NoticeReceiver> specification = (root, query, criteriaBuilder) -> {
|
||||
Predicate predicate = criteriaBuilder.conjunction();
|
||||
if (StringUtils.isNotBlank(name)) {
|
||||
Predicate predicateName = criteriaBuilder.like(
|
||||
criteriaBuilder.lower(root.get("name")), "%" + name.toLowerCase() + "%"
|
||||
);
|
||||
Predicate predicateName = criteriaBuilder.like(root.get("name"), "%" + name + "%");
|
||||
predicate = criteriaBuilder.and(predicateName);
|
||||
}
|
||||
return predicate;
|
||||
};
|
||||
return noticeReceiverDao.findAll(specification, PageRequest.of(pageIndex, pageSize, Sort.by(Sort.Direction.DESC, "id")));
|
||||
return noticeReceiverDao.findAll(specification);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<NoticeReceiver> getAllNoticeReceivers() {
|
||||
return noticeReceiverDao.findAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<NoticeTemplate> getNoticeTemplates(String name, boolean preset, int pageIndex, int pageSize) {
|
||||
if (preset) {
|
||||
// Query preset templates
|
||||
List<NoticeTemplate> defaultTemplates = new LinkedList<>(PRESET_TEMPLATE.values());
|
||||
|
||||
// Filter by name (case-insensitive)
|
||||
List<NoticeTemplate> filteredDefaultTemplates = defaultTemplates.stream()
|
||||
.filter(template -> StringUtils.isBlank(name)
|
||||
|| template.getName().toLowerCase().contains(name.toLowerCase()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// Pagination logic
|
||||
int totalItems = filteredDefaultTemplates.size();
|
||||
int fromIndex = Math.min(pageIndex * pageSize, totalItems);
|
||||
int toIndex = Math.min(fromIndex + pageSize, totalItems);
|
||||
|
||||
if (fromIndex >= totalItems) {
|
||||
return new PageImpl<>(Collections.emptyList(), PageRequest.of(pageIndex, pageSize), totalItems);
|
||||
public List<NoticeTemplate> getNoticeTemplates(String name) {
|
||||
Specification<NoticeTemplate> specification = (root, query, criteriaBuilder) -> {
|
||||
Predicate predicate = criteriaBuilder.conjunction();
|
||||
if (StringUtils.isNotBlank(name)) {
|
||||
Predicate predicateName = criteriaBuilder.like(root.get("name"), "%" + name + "%");
|
||||
predicate = criteriaBuilder.and(predicateName);
|
||||
}
|
||||
|
||||
List<NoticeTemplate> paginatedTemplates = filteredDefaultTemplates.subList(fromIndex, toIndex);
|
||||
return new PageImpl<>(paginatedTemplates, PageRequest.of(pageIndex, pageSize), totalItems);
|
||||
} else {
|
||||
// Query custom templates
|
||||
Specification<NoticeTemplate> specification = (root, query, criteriaBuilder) -> {
|
||||
Predicate predicate = criteriaBuilder.conjunction();
|
||||
if (StringUtils.isNotBlank(name)) {
|
||||
Predicate predicateName = criteriaBuilder.like(
|
||||
criteriaBuilder.lower(root.get("name")), "%" + name.toLowerCase() + "%"
|
||||
);
|
||||
predicate = criteriaBuilder.and(predicateName);
|
||||
}
|
||||
return predicate;
|
||||
};
|
||||
PageRequest pageRequest = PageRequest.of(pageIndex, pageSize, Sort.by(Sort.Direction.DESC, "id"));
|
||||
return noticeTemplateDao.findAll(specification, pageRequest);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public List<NoticeTemplate> getAllNoticeTemplates() {
|
||||
return predicate;
|
||||
};
|
||||
List<NoticeTemplate> defaultTemplates = new LinkedList<>(PRESET_TEMPLATE.values());
|
||||
defaultTemplates.addAll(noticeTemplateDao.findAll());
|
||||
defaultTemplates.addAll(noticeTemplateDao.findAll(specification));
|
||||
return defaultTemplates;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<NoticeRule> getNoticeRules(String name, int pageIndex, int pageSize) {
|
||||
public List<NoticeRule> getNoticeRules(String name) {
|
||||
Specification<NoticeRule> specification = (root, query, criteriaBuilder) -> {
|
||||
Predicate predicate = criteriaBuilder.conjunction();
|
||||
if (StringUtils.isNotBlank(name)) {
|
||||
Predicate predicateName = criteriaBuilder.like(
|
||||
criteriaBuilder.lower(root.get("name")), "%" + name.toLowerCase() + "%"
|
||||
);
|
||||
Predicate predicateName = criteriaBuilder.like(root.get("name"), "%" + name + "%");
|
||||
predicate = criteriaBuilder.and(predicateName);
|
||||
}
|
||||
return predicate;
|
||||
};
|
||||
return noticeRuleDao.findAll(specification, PageRequest.of(pageIndex, pageSize, Sort.by(Sort.Direction.DESC, "id")));
|
||||
return noticeRuleDao.findAll(specification);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -305,7 +256,7 @@ public class NoticeConfigServiceImpl implements NoticeConfigService, CommandLine
|
||||
Map<String, String> labels = new HashMap<>(8);
|
||||
labels.put(CommonConstants.LABEL_INSTANCE, "1000000");
|
||||
labels.put(CommonConstants.LABEL_ALERT_NAME, "CPU Usage Alert");
|
||||
labels.put(CommonConstants.LABEL_INSTANCE_HOST, "127.0.0.1");
|
||||
labels.put(CommonConstants.LABEL_HOST, "127.0.0.1");
|
||||
Map<String, String> annotations = new HashMap<>(8);
|
||||
annotations.put("suggest", "Please check the CPU usage of the server");
|
||||
SingleAlert singleAlert1 = SingleAlert.builder()
|
||||
|
||||
-95
@@ -1,95 +0,0 @@
|
||||
/*
|
||||
* 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.core.type.TypeReference;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.alert.dto.SkyWalkingExternAlert;
|
||||
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.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* SkyWalking external alarm service impl
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class SkyWalkingExternAlertService implements ExternAlertService {
|
||||
|
||||
@Autowired
|
||||
private AlarmCommonReduce alarmCommonReduce;
|
||||
|
||||
|
||||
@Override
|
||||
public void addExternAlert(String content) {
|
||||
TypeReference<List<SkyWalkingExternAlert>> typeReference = new TypeReference<>() {};
|
||||
List<SkyWalkingExternAlert> alerts = JsonUtil.fromJson(content, typeReference);
|
||||
if (alerts == null || alerts.isEmpty()) {
|
||||
log.warn("Parse SkyWalking extern alert content failed! content: {}", content);
|
||||
return;
|
||||
}
|
||||
for (SkyWalkingExternAlert alert : alerts) {
|
||||
SingleAlert singleAlert = SingleAlert.builder()
|
||||
.content(alert.getAlarmMessage())
|
||||
.status(CommonConstants.ALERT_STATUS_FIRING)
|
||||
.activeAt(Instant.now().toEpochMilli())
|
||||
.startAt(alert.getStartTime() != null ? alert.getStartTime() : Instant.now().toEpochMilli())
|
||||
.labels(acquireAlertLabels(alert))
|
||||
.annotations(acquireAlertAnnotations(alert))
|
||||
.triggerTimes(1)
|
||||
.build();
|
||||
alarmCommonReduce.reduceAndSendAlarm(singleAlert);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String supportSource() {
|
||||
return "skywalking";
|
||||
}
|
||||
|
||||
private Map<String, String> acquireAlertLabels(SkyWalkingExternAlert externAlert){
|
||||
Map<String, String> labels = new HashMap<>(8);
|
||||
labels.put("__source__", "skywalking");
|
||||
List<SkyWalkingExternAlert.Tag> tags = externAlert.getTags();
|
||||
if (tags == null || tags.isEmpty()){
|
||||
return labels;
|
||||
}
|
||||
tags.forEach(tag -> labels.put(tag.getKey(), tag.getValue()));
|
||||
return labels;
|
||||
}
|
||||
|
||||
private Map<String, String> acquireAlertAnnotations(SkyWalkingExternAlert externAlert){
|
||||
Map<String, String> annotations = new HashMap<>(8);
|
||||
annotations.putIfAbsent("scope", externAlert.getScope());
|
||||
annotations.putIfAbsent("name", externAlert.getName());
|
||||
annotations.putIfAbsent("id0", externAlert.getId0());
|
||||
annotations.putIfAbsent("id1", externAlert.getId1());
|
||||
annotations.putIfAbsent("ruleName", externAlert.getRuleName());
|
||||
return annotations;
|
||||
}
|
||||
|
||||
}
|
||||
-146
@@ -1,146 +0,0 @@
|
||||
/*
|
||||
* 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.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.alert.config.SmslocalSmsProperties;
|
||||
import org.apache.hertzbeat.alert.service.SmsClient;
|
||||
import org.apache.hertzbeat.common.constants.SmsConstants;
|
||||
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
|
||||
import org.apache.hertzbeat.common.support.exception.SendMessageException;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Smslocal SMS Client Implement
|
||||
*/
|
||||
@Slf4j
|
||||
public class SmsLocalSmsClientImpl implements SmsClient {
|
||||
private static final String HOST = "secure.smslocal.com";
|
||||
|
||||
private static final String PATH = "/api/service/enterprise-service/external/sms";
|
||||
|
||||
private static final String FROM = "Hertzbeat";
|
||||
|
||||
private static final String SUCCESS_CODE = "200";
|
||||
|
||||
private final SmslocalSmsProperties config;
|
||||
|
||||
public SmsLocalSmsClientImpl(SmslocalSmsProperties smslocalSmsProperties) {
|
||||
this.config = smslocalSmsProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) {
|
||||
if (Objects.isNull(receiver) || Objects.isNull(alert)) {
|
||||
log.warn("receiver and alert can not be null! receiver: {}, alert:{}", receiver, alert);
|
||||
return;
|
||||
}
|
||||
|
||||
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
|
||||
String content = alert.getCommonAnnotations().get("summary");
|
||||
if (Objects.isNull(content) || Objects.isNull(alert.getCommonAnnotations().get("description"))) {
|
||||
content = alert.getAlerts().get(0).getContent();
|
||||
}
|
||||
SmsMessage smsMessage = new SmsMessage(FROM, receiver.getPhone(), content);
|
||||
|
||||
String payload = JsonUtil.toJson(smsMessage);
|
||||
|
||||
HttpPost httpPost = new HttpPost("https://" + HOST + PATH);
|
||||
httpPost.setHeader("Content-Type", "application/json; charset=utf-8");
|
||||
httpPost.setHeader("Token", config.getApiKey());
|
||||
httpPost.setEntity(new StringEntity(payload, StandardCharsets.UTF_8));
|
||||
|
||||
log.debug("Sending SMS request to {}, payload: {}", httpPost.getURI(), payload);
|
||||
|
||||
// send http request and handle response
|
||||
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
String responseBody = EntityUtils.toString(response.getEntity());
|
||||
|
||||
log.debug("SMS response status: {}, body: {}", statusCode, responseBody);
|
||||
|
||||
if (statusCode != 200) {
|
||||
throw new SendMessageException("HTTP request failed with status code: " + statusCode);
|
||||
}
|
||||
|
||||
JsonNode jsonResponse = JsonUtil.fromJson(responseBody);
|
||||
JsonNode jsonNode = jsonResponse.get(0);
|
||||
if (Objects.isNull(jsonNode)) {
|
||||
log.warn("jsonResponse parse errorCode failed: {}", jsonResponse);
|
||||
return;
|
||||
}
|
||||
String errorCode = jsonNode.get("errorCode").asText();
|
||||
if (!SUCCESS_CODE.equals(errorCode)) {
|
||||
String msgid = jsonNode.get("id").asText();
|
||||
throw new SendMessageException(errorCode + ":" + msgid);
|
||||
}
|
||||
|
||||
log.info("Successfully sent SMS to phone: {}", receiver.getPhone());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to send SMS: {}", e.getMessage());
|
||||
throw new SendMessageException(e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return SmsConstants.SMSLOCAL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkConfig() {
|
||||
if (Objects.isNull(config) || Objects.isNull(config.getApiKey()) || config.getApiKey().isBlank()) {
|
||||
log.warn("smslocal properties can not be null: {}", config);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private static class SmsMessage {
|
||||
String from;
|
||||
String to;
|
||||
String content;
|
||||
final int datacoding = 0;
|
||||
final String direction = "mt";
|
||||
|
||||
public SmsMessage(String from, String to, String content) {
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
this.content = content;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+2
-174
@@ -18,22 +18,14 @@
|
||||
package org.apache.hertzbeat.alert.service.impl;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.hertzbeat.alert.dto.TencentCloudExternAlert;
|
||||
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.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Default external alarm service impl
|
||||
*/
|
||||
@@ -51,7 +43,8 @@ public class TencentExternAlertService implements ExternAlertService {
|
||||
log.warn("parse extern alert content failed! content: {}", content);
|
||||
return;
|
||||
}
|
||||
SingleAlert alert = new TencentCloudAlertConverter().convert(report);
|
||||
// todo convert TenCloudAlertReport to SingleAlert
|
||||
SingleAlert alert = new SingleAlert();
|
||||
alarmCommonReduce.reduceAndSendAlarm(alert);
|
||||
}
|
||||
|
||||
@@ -59,169 +52,4 @@ public class TencentExternAlertService implements ExternAlertService {
|
||||
public String supportSource() {
|
||||
return "tencent";
|
||||
}
|
||||
|
||||
/**
|
||||
* Converter: TencentCloud alert to SingleAlert
|
||||
*/
|
||||
public static class TencentCloudAlertConverter {
|
||||
|
||||
/**
|
||||
* Metric alert content template
|
||||
*/
|
||||
private final String metricTemplate = "Tencent Cloud Metric Alert: {metricShowName} {calcType} {calcValue}{calcUnit}, Current: {currentValue}{unit}";
|
||||
|
||||
/**
|
||||
* Event alert content template
|
||||
*/
|
||||
private final String eventTemplate = "Tencent Cloud Event Alert: {productShowName} - {eventShowName}";
|
||||
|
||||
|
||||
/**
|
||||
* Convert TencentCloud alert to SingleAlert
|
||||
*/
|
||||
public SingleAlert convert(TencentCloudExternAlert tencentAlert) {
|
||||
// Building basic information
|
||||
SingleAlert.SingleAlertBuilder builder = SingleAlert.builder()
|
||||
.status(convertStatus(tencentAlert.getAlarmStatus()))
|
||||
.startAt(parseTime(tencentAlert.getFirstOccurTime()))
|
||||
.activeAt(parseTime(tencentAlert.getFirstOccurTime()))
|
||||
.triggerTimes(1);
|
||||
// If it is a recovery state, set the end time.
|
||||
if ("0".equals(tencentAlert.getAlarmStatus())) {
|
||||
builder.endAt(parseTime(tencentAlert.getRecoverTime()));
|
||||
}
|
||||
|
||||
// Build labels
|
||||
Map<String, String> labels = new HashMap<>();
|
||||
buildLabels(labels, tencentAlert);
|
||||
|
||||
// Build annotations
|
||||
Map<String, String> annotations = new HashMap<>();
|
||||
buildAnnotations(annotations, tencentAlert);
|
||||
|
||||
// Build content
|
||||
String content = generateContent(tencentAlert);
|
||||
|
||||
return builder
|
||||
.labels(labels)
|
||||
.annotations(annotations)
|
||||
.content(content)
|
||||
.build();
|
||||
}
|
||||
|
||||
private void buildLabels(Map<String, String> labels, TencentCloudExternAlert alert) {
|
||||
labels.put("__source__", "tencent_cloud");
|
||||
labels.put("alert_type", alert.getAlarmType());
|
||||
if (TencentCloudExternAlert.METRIC.equals(alert.getAlarmType())) {
|
||||
labels.put("metric_name", alert.getAlarmPolicyInfo().getConditions().getMetricName());
|
||||
labels.put("namespace", alert.getAlarmObjInfo().getNamespace());
|
||||
} else {
|
||||
labels.put("event_name", alert.getAlarmPolicyInfo().getConditions().getEventName());
|
||||
labels.put("product_name", alert.getAlarmPolicyInfo().getConditions().getProductName());
|
||||
}
|
||||
}
|
||||
|
||||
private void buildAnnotations(Map<String, String> annotations, TencentCloudExternAlert alert) {
|
||||
TencentCloudExternAlert.AlarmPolicyInfo alarmPolicyInfo = alert.getAlarmPolicyInfo();
|
||||
TencentCloudExternAlert.AlarmObjInfo alarmObjInfo = alert.getAlarmObjInfo();
|
||||
TencentCloudExternAlert.Dimensions dimensions = alert.getAlarmObjInfo().getDimensions();
|
||||
|
||||
putIfNotNull(annotations, "policy_id", alarmPolicyInfo.getPolicyId());
|
||||
putIfNotNull(annotations, "policy_type", alarmPolicyInfo.getPolicyType());
|
||||
putIfNotNull(annotations, "policy_name", alarmPolicyInfo.getPolicyName());
|
||||
putIfNotNull(annotations, "policy_type_cname", alarmPolicyInfo.getPolicyTypeCname());
|
||||
|
||||
putIfNotNull(annotations, "namespace", alarmObjInfo.getNamespace());
|
||||
putIfNotNull(annotations, "region", alarmObjInfo.getRegion());
|
||||
putIfNotNull(annotations, "app_id", alarmObjInfo.getAppId());
|
||||
putIfNotNull(annotations, "uin", alarmObjInfo.getUin());
|
||||
|
||||
putIfNotNull(annotations, "instance_id", dimensions.getUnInstanceId());
|
||||
putIfNotNull(annotations, "obj_id", dimensions.getObjId());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate alert content with template
|
||||
*/
|
||||
private String generateContent(TencentCloudExternAlert alert) {
|
||||
if (TencentCloudExternAlert.METRIC.equals(alert.getAlarmType())) {
|
||||
return generateMetricContent(alert);
|
||||
} else {
|
||||
return generateEventContent(alert);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate metric alert content
|
||||
*/
|
||||
private String generateMetricContent(TencentCloudExternAlert alert) {
|
||||
TencentCloudExternAlert.Conditions conditions = alert.getAlarmPolicyInfo().getConditions();
|
||||
Map<String, String> params = new HashMap<>();
|
||||
|
||||
params.put("metricShowName", conditions.getMetricShowName());
|
||||
|
||||
if (StringUtils.isNotEmpty(conditions.getCalcType())
|
||||
&& StringUtils.isNotEmpty(conditions.getCalcValue())) {
|
||||
params.put("calcType", conditions.getCalcType());
|
||||
params.put("calcValue", conditions.getCalcValue());
|
||||
params.put("calcUnit", Objects.toString(conditions.getCalcUnit(), ""));
|
||||
} else {
|
||||
params.put("calcType", "");
|
||||
params.put("calcValue", "N/A");
|
||||
params.put("calcUnit", "");
|
||||
}
|
||||
|
||||
if (StringUtils.isNotEmpty(conditions.getCurrentValue())) {
|
||||
params.put("currentValue", conditions.getCurrentValue());
|
||||
params.put("unit", Objects.toString(conditions.getUnit(), ""));
|
||||
} else {
|
||||
params.put("currentValue", "N/A");
|
||||
params.put("unit", "");
|
||||
}
|
||||
return replacePlaceholders(metricTemplate, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate event alert content
|
||||
*/
|
||||
private String generateEventContent(TencentCloudExternAlert alert) {
|
||||
TencentCloudExternAlert.Conditions conditions = alert.getAlarmPolicyInfo().getConditions();
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("productShowName", conditions.getProductShowName());
|
||||
params.put("eventShowName", conditions.getEventShowName());
|
||||
return replacePlaceholders(eventTemplate, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace placeholders in template with actual values
|
||||
*/
|
||||
private String replacePlaceholders(String template, Map<String, String> params) {
|
||||
String result = template;
|
||||
for (Map.Entry<String, String> entry : params.entrySet()) {
|
||||
result = result.replace("{" + entry.getKey() + "}", Objects.toString(entry.getValue(), "NULL"));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private String convertStatus(String alarmStatus) {
|
||||
return "1".equals(alarmStatus) ? CommonConstants.ALERT_STATUS_FIRING : CommonConstants.ALERT_STATUS_RESOLVED;
|
||||
}
|
||||
|
||||
private Long parseTime(String timeStr) {
|
||||
try {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
return sdf.parse(timeStr).getTime();
|
||||
} catch (ParseException e) {
|
||||
log.error("Failed to parse time: {}", timeStr);
|
||||
throw new IllegalArgumentException("Failed to parse time: " + timeStr, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void putIfNotNull(Map<String, String> map, String key, String value){
|
||||
if (StringUtils.isNotEmpty(value)){
|
||||
map.put(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-223
@@ -1,223 +0,0 @@
|
||||
/*
|
||||
* 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 jakarta.xml.bind.DatatypeConverter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.alert.config.TencentSmsProperties;
|
||||
import org.apache.hertzbeat.alert.service.SmsClient;
|
||||
import org.apache.hertzbeat.alert.util.CryptoUtils;
|
||||
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
|
||||
import org.apache.hertzbeat.common.support.exception.SendMessageException;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import static org.apache.hertzbeat.common.constants.SmsConstants.TENCENT;
|
||||
|
||||
/**
|
||||
* sms service client for tencent cloud <br>
|
||||
* doc: <a href="https://cloud.tencent.com/document/api/382/55981">https://cloud.tencent.com/document/api/382/55981</a>
|
||||
*/
|
||||
|
||||
@Slf4j
|
||||
public class TencentSmsClientImpl implements SmsClient {
|
||||
|
||||
private static final String RESPONSE_OK = "Ok";
|
||||
private static final String REGION = "ap-guangzhou";
|
||||
private static final String API_VERSION = "2021-01-11";
|
||||
private static final String ACTION = "SendSms";
|
||||
private static final String HOST = "sms.tencentcloudapi.com";
|
||||
private static final Charset UTF8 = StandardCharsets.UTF_8;
|
||||
|
||||
private String appId;
|
||||
private String signName;
|
||||
private String templateId;
|
||||
private String secretId;
|
||||
private String secretKey;
|
||||
|
||||
public TencentSmsClientImpl(TencentSmsProperties config) {
|
||||
if (config != null) {
|
||||
this.appId = config.getAppId();
|
||||
this.signName = config.getSignName();
|
||||
this.templateId = config.getTemplateId();
|
||||
this.secretId = config.getSecretId();
|
||||
this.secretKey = config.getSecretKey();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) {
|
||||
// todo limit the number of words
|
||||
String instance = null;
|
||||
String priority = null;
|
||||
String content = null;
|
||||
if (alert.getCommonLabels() != null) {
|
||||
instance = alert.getCommonLabels().get("instance");
|
||||
priority = alert.getCommonLabels().get("priority");
|
||||
content = alert.getCommonAnnotations().get("summary");
|
||||
content = content == null ? alert.getCommonAnnotations().get("description") : content;
|
||||
if (content == null) {
|
||||
content = alert.getCommonAnnotations().values().stream().findFirst().orElse(null);
|
||||
}
|
||||
}
|
||||
|
||||
String[] templateValues = new String[3];
|
||||
templateValues[0] = instance == null ? alert.getGroupKey() : instance;
|
||||
templateValues[1] = priority == null ? "unknown" : priority;
|
||||
templateValues[2] = content;
|
||||
|
||||
String[] phones = new String[1];
|
||||
phones[0] = receiver.getPhone();
|
||||
|
||||
sendSms(this.appId, this.signName, this.templateId, templateValues, phones);
|
||||
}
|
||||
|
||||
public void sendSms(String appId, String signName, String templateId,
|
||||
String[] templateValues, String[] phones) {
|
||||
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
|
||||
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
|
||||
|
||||
// build request payload
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("SmsSdkAppId", appId);
|
||||
params.put("SignName", signName);
|
||||
params.put("TemplateId", templateId);
|
||||
params.put("TemplateParamSet", templateValues);
|
||||
params.put("PhoneNumberSet", phones);
|
||||
|
||||
String payload = JsonUtil.toJson(params);
|
||||
|
||||
// calculate request signature
|
||||
String authorization = calculateAuthorization(
|
||||
secretId, secretKey, "sms", HOST, REGION,
|
||||
ACTION, API_VERSION, payload);
|
||||
|
||||
// build http request
|
||||
HttpPost httpPost = new HttpPost("https://" + HOST);
|
||||
httpPost.setHeader("Content-Type", "application/json; charset=utf-8");
|
||||
httpPost.setHeader("Host", HOST);
|
||||
httpPost.setHeader("X-TC-Action", ACTION);
|
||||
httpPost.setHeader("X-TC-Timestamp", timestamp);
|
||||
httpPost.setHeader("X-TC-Version", API_VERSION);
|
||||
httpPost.setHeader("X-TC-Region", REGION);
|
||||
httpPost.setHeader("Authorization", authorization);
|
||||
httpPost.setEntity(new StringEntity(payload, StandardCharsets.UTF_8));
|
||||
|
||||
log.debug("Sending SMS request to {}, payload: {}", httpPost.getURI(), payload);
|
||||
|
||||
// send http request and handle response
|
||||
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
String responseBody = EntityUtils.toString(response.getEntity());
|
||||
|
||||
log.debug("SMS response status: {}, body: {}", statusCode, responseBody);
|
||||
|
||||
if (statusCode != 200) {
|
||||
throw new SendMessageException("HTTP request failed with status code: " + statusCode);
|
||||
}
|
||||
|
||||
JsonNode jsonResponse = JsonUtil.fromJson(responseBody);
|
||||
JsonNode responseNode = jsonResponse.get("Response");
|
||||
JsonNode error = responseNode.get("Error");
|
||||
if (error != null) {
|
||||
String code = error.get("Code").asText();
|
||||
String message = error.get("Message").asText();
|
||||
throw new SendMessageException(code + ":" + message);
|
||||
}
|
||||
JsonNode sendStatusSet = responseNode.get("SendStatusSet");
|
||||
if (sendStatusSet != null && sendStatusSet.isArray() && sendStatusSet.size() > 0) {
|
||||
JsonNode firstStatus = sendStatusSet.get(0);
|
||||
String code = firstStatus.get("Code").asText();
|
||||
String message = firstStatus.get("Message").asText();
|
||||
if (!RESPONSE_OK.equals(code)) {
|
||||
throw new SendMessageException(code + ":" + message);
|
||||
}
|
||||
}
|
||||
log.info("Successfully sent SMS to phones: {}", String.join(",", phones));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to send SMS: {}", e.getMessage());
|
||||
throw new SendMessageException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return TENCENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkConfig() {
|
||||
if (appId.isBlank() || templateId.isBlank() || secretId.isBlank() || secretKey.isBlank()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static String calculateAuthorization(String secretId, String secretKey,
|
||||
String service, String host, String region,
|
||||
String action, String version, String payload) throws Exception {
|
||||
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
|
||||
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
String date = sdf.format(new Date(Long.valueOf(timestamp + "000")));
|
||||
|
||||
// Step 1: Construct the canonical request string
|
||||
String httpRequestMethod = "POST";
|
||||
String canonicalUri = "/";
|
||||
String canonicalQueryString = "";
|
||||
String canonicalHeaders = "content-type:application/json; charset=utf-8\n" + "host:" + host + "\n";
|
||||
String signedHeaders = "content-type;host";
|
||||
String hashedRequestPayload = CryptoUtils.sha256Hex(payload);
|
||||
String canonicalRequest = httpRequestMethod + "\n" + canonicalUri + "\n" + canonicalQueryString + "\n"
|
||||
+ canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestPayload;
|
||||
|
||||
// Step 2: Construct the string to sign
|
||||
String algorithm = "TC3-HMAC-SHA256";
|
||||
String credentialScope = date + "/" + service + "/" + "tc3_request";
|
||||
String hashedCanonicalRequest = CryptoUtils.sha256Hex(canonicalRequest);
|
||||
String stringToSign = algorithm + "\n" + timestamp + "\n" + credentialScope + "\n" + hashedCanonicalRequest;
|
||||
|
||||
// Step 3: Calculate the signature
|
||||
byte[] secretDate = CryptoUtils.hmac256(("TC3" + secretKey).getBytes(UTF8), date);
|
||||
byte[] secretService = CryptoUtils.hmac256(secretDate, service);
|
||||
byte[] secretSigning = CryptoUtils.hmac256(secretService, "tc3_request");
|
||||
String signature = DatatypeConverter.printHexBinary(
|
||||
CryptoUtils.hmac256(secretSigning, stringToSign)).toLowerCase();
|
||||
|
||||
// Step 4: Construct the Authorization header
|
||||
return algorithm + " " + "Credential=" + secretId + "/" + credentialScope + ", "
|
||||
+ "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature;
|
||||
}
|
||||
}
|
||||
-171
@@ -1,171 +0,0 @@
|
||||
/*
|
||||
* 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.hertzbeat.alert.config.TwilioSmsProperties;
|
||||
import org.apache.hertzbeat.alert.service.SmsClient;
|
||||
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
|
||||
import org.apache.hertzbeat.common.support.exception.SendMessageException;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
|
||||
import static org.apache.hertzbeat.common.constants.SmsConstants.TWILIO;
|
||||
|
||||
/**
|
||||
* Twilio SMS Client Implementation<br>
|
||||
* API doc: <a href=
|
||||
* "https://www.twilio.com/docs/sms/api">https://www.twilio.com/docs/sms/api</a>
|
||||
*/
|
||||
@Slf4j
|
||||
public class TwilioSmsClientImpl implements SmsClient {
|
||||
|
||||
private static final String API_URL_FORMAT = "https://api.twilio.com/2010-04-01/Accounts/%s/Messages.json";
|
||||
private static final String MESSAGE_TEMPLATE = "Instance: %s, Priority: %s, Content: %s";
|
||||
|
||||
private final String accountSid;
|
||||
private final String authToken;
|
||||
private final String twilioPhoneNumber;
|
||||
|
||||
public TwilioSmsClientImpl(TwilioSmsProperties config) {
|
||||
if (config != null) {
|
||||
this.accountSid = config.getAccountSid();
|
||||
this.authToken = config.getAuthToken();
|
||||
this.twilioPhoneNumber = config.getTwilioPhoneNumber();
|
||||
} else {
|
||||
this.accountSid = "";
|
||||
this.authToken = "";
|
||||
this.twilioPhoneNumber = "";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) {
|
||||
String instance = null;
|
||||
String priority = null;
|
||||
String content = null;
|
||||
if (alert.getCommonLabels() != null) {
|
||||
instance = alert.getCommonLabels().get("instance") == null ? alert.getGroupKey()
|
||||
: alert.getCommonLabels().get("instance");
|
||||
priority = alert.getCommonLabels().get("priority") == null ? "unknown"
|
||||
: alert.getCommonLabels().get("priority");
|
||||
content = alert.getCommonAnnotations().get("summary");
|
||||
content = content == null ? alert.getCommonAnnotations().get("description") : content;
|
||||
if (content == null) {
|
||||
content = alert.getCommonAnnotations().values().stream().findFirst().orElse(null);
|
||||
}
|
||||
}
|
||||
this.send(receiver.getPhone(), createMessage(instance, priority, content));
|
||||
}
|
||||
|
||||
private String createMessage(String instance, String priority, String content) {
|
||||
return String.format(MESSAGE_TEMPLATE, instance, priority, content);
|
||||
}
|
||||
|
||||
private void send(String phoneNumber, String message) {
|
||||
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
|
||||
String endpoint = String.format(API_URL_FORMAT, accountSid);
|
||||
URI requestUri = new URI(endpoint);
|
||||
|
||||
HttpPost httpPost = createHttpPost(requestUri, phoneNumber, message);
|
||||
log.info("Sending Twilio SMS request to {}", requestUri);
|
||||
executeRequest(httpClient, httpPost, phoneNumber);
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to send SMS: {}", e.getMessage());
|
||||
throw new SendMessageException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private HttpPost createHttpPost(URI requestUri, String toNumber, String message) {
|
||||
HttpPost httpPost = new HttpPost(requestUri);
|
||||
|
||||
String auth = accountSid + ":" + authToken;
|
||||
String encodedAuth = Base64.getEncoder().encodeToString(auth.getBytes());
|
||||
httpPost.setHeader("Authorization", "Basic " + encodedAuth);
|
||||
|
||||
List<BasicNameValuePair> parameters = new ArrayList<>();
|
||||
parameters.add(new BasicNameValuePair("To", toNumber));
|
||||
parameters.add(new BasicNameValuePair("From", twilioPhoneNumber));
|
||||
parameters.add(new BasicNameValuePair("Body", message));
|
||||
|
||||
try {
|
||||
httpPost.setEntity(new UrlEncodedFormEntity(parameters));
|
||||
return httpPost;
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to create HTTP request: {}", e.getMessage());
|
||||
throw new SendMessageException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void executeRequest(CloseableHttpClient httpClient, HttpPost httpPost, String phoneNumber)
|
||||
throws Exception {
|
||||
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
String responseBody = EntityUtils.toString(response.getEntity());
|
||||
log.info("SMS response status: {}, body: {}", statusCode, responseBody);
|
||||
|
||||
if (statusCode < 200 || statusCode >= 300) {
|
||||
|
||||
if (responseBody.contains("21608")) {
|
||||
throw new SendMessageException(
|
||||
"The Twilio trial account can only send SMS to verified phone numbers");
|
||||
} else {
|
||||
throw new SendMessageException(
|
||||
"HTTP request failed with status code: " + statusCode + ", response: " + responseBody);
|
||||
}
|
||||
}
|
||||
|
||||
JsonNode jsonResponse = JsonUtil.fromJson(responseBody);
|
||||
if (jsonResponse == null) {
|
||||
throw new SendMessageException(statusCode + ":" + responseBody);
|
||||
}
|
||||
|
||||
JsonNode sidNode = jsonResponse.get("sid");
|
||||
if (sidNode == null) {
|
||||
throw new SendMessageException(statusCode + ":" + responseBody);
|
||||
}
|
||||
|
||||
String sid = sidNode.asText();
|
||||
log.info("Successfully sent SMS to phone: {}, sid: {}", phoneNumber, sid);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return TWILIO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkConfig() {
|
||||
return !(accountSid.isBlank() || authToken.isBlank() || twilioPhoneNumber.isBlank());
|
||||
}
|
||||
}
|
||||
-193
@@ -1,193 +0,0 @@
|
||||
/*
|
||||
* 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.hertzbeat.alert.config.UniSmsProperties;
|
||||
import org.apache.hertzbeat.alert.service.SmsClient;
|
||||
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
|
||||
import org.apache.hertzbeat.common.support.exception.SendMessageException;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.apache.hertzbeat.alert.util.CryptoUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.apache.hertzbeat.common.constants.SmsConstants.UNISMS;
|
||||
|
||||
/**
|
||||
* UniSMS client implementation <br/>
|
||||
* doc:<a href="https://unisms.apistd.com/docs/api/send">https://unisms.apistd.com/docs/api/send</a>
|
||||
*/
|
||||
@Slf4j
|
||||
public class UniSmsClientImpl implements SmsClient {
|
||||
|
||||
private static final String API_URL = "https://uni.apistd.com";
|
||||
private static final String ACTION = "sms.message.send";
|
||||
private static final String SUCCESS_CODE = "0";
|
||||
private static final String HMAC_ALGORITHM = "hmac-sha256";
|
||||
|
||||
private final UniSmsProperties config;
|
||||
|
||||
public UniSmsClientImpl(UniSmsProperties config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) {
|
||||
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
|
||||
// build request parameters
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("to", receiver.getPhone());
|
||||
params.put("signature", config.getSignature());
|
||||
params.put("templateId", config.getTemplateId());
|
||||
|
||||
// build template data
|
||||
Map<String, String> templateData = new HashMap<>();
|
||||
String instance = alert.getCommonLabels().getOrDefault("instance", alert.getGroupKey());
|
||||
String priority = alert.getCommonLabels().getOrDefault("priority", "unknown");
|
||||
String content = alert.getCommonAnnotations().get("summary");
|
||||
content = content == null ? alert.getCommonAnnotations().get("description") : content;
|
||||
if (content == null) {
|
||||
content = alert.getCommonAnnotations().values().stream().findFirst().orElse(null);
|
||||
}
|
||||
|
||||
templateData.put("instance", instance);
|
||||
templateData.put("priority", priority);
|
||||
templateData.put("content", content);
|
||||
params.put("templateData", templateData);
|
||||
|
||||
// build URL and request headers
|
||||
String url;
|
||||
if ("hmac".equalsIgnoreCase(config.getAuthMode())) {
|
||||
url = buildHmacUrl();
|
||||
} else {
|
||||
url = buildSimpleUrl();
|
||||
}
|
||||
|
||||
// send HTTP request
|
||||
HttpPost httpPost = new HttpPost(url);
|
||||
httpPost.setHeader("Content-Type", "application/json");
|
||||
httpPost.setHeader("Accept", "application/json");
|
||||
|
||||
String payload = JsonUtil.toJson(params);
|
||||
httpPost.setEntity(new StringEntity(payload, StandardCharsets.UTF_8));
|
||||
|
||||
log.info("Sending SMS request to UniSMS, payload: {}, url: {}", payload, url);
|
||||
|
||||
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
|
||||
handleResponse(response, receiver.getPhone());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to send SMS via UniSMS: {}", e.getMessage());
|
||||
throw new SendMessageException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private String buildSimpleUrl() {
|
||||
return String.format("%s/?action=%s&accessKeyId=%s",
|
||||
API_URL, ACTION, config.getAccessKeyId());
|
||||
}
|
||||
|
||||
private String buildHmacUrl() {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
String nonce = generateNonce();
|
||||
|
||||
// build query parameters
|
||||
Map<String, String> params = new TreeMap<>();
|
||||
params.put("accessKeyId", config.getAccessKeyId());
|
||||
params.put("action", ACTION);
|
||||
params.put("algorithm", HMAC_ALGORITHM);
|
||||
params.put("nonce", nonce);
|
||||
params.put("timestamp", String.valueOf(timestamp));
|
||||
|
||||
// build sign text
|
||||
String signText = params.entrySet().stream()
|
||||
.map(entry -> entry.getKey() + "=" + entry.getValue())
|
||||
.collect(Collectors.joining("&"));
|
||||
|
||||
// calculate signature
|
||||
String signature = CryptoUtils.hmacSha256Base64(config.getAccessKeySecret(), signText);
|
||||
return String.format("%s/?action=%s&accessKeyId=%s&algorithm=%s×tamp=%d&nonce=%s&signature=%s",
|
||||
API_URL, ACTION, config.getAccessKeyId(), HMAC_ALGORITHM, timestamp, nonce, signature);
|
||||
}
|
||||
|
||||
private String generateNonce() {
|
||||
return UUID.randomUUID().toString().replace("-", "").substring(0, 16);
|
||||
}
|
||||
|
||||
private void handleResponse(CloseableHttpResponse response, String phone) throws IOException {
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
String responseBody = EntityUtils.toString(response.getEntity());
|
||||
|
||||
log.info("UniSMS response status: {}, body: {}", statusCode, responseBody);
|
||||
|
||||
if (statusCode != 200) {
|
||||
throw new SendMessageException("HTTP request failed with status code: " + statusCode + ", response: " + responseBody);
|
||||
}
|
||||
|
||||
JsonNode jsonResponse = JsonUtil.fromJson(responseBody);
|
||||
String code = jsonResponse.get("code").asText();
|
||||
if (!SUCCESS_CODE.equals(code)) {
|
||||
String message = jsonResponse.get("message").asText();
|
||||
throw new SendMessageException(code + ":" + message);
|
||||
}
|
||||
|
||||
log.info("Successfully sent SMS to phone: {}", phone);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return UNISMS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkConfig() {
|
||||
if (config == null
|
||||
|| config.getAccessKeyId() == null
|
||||
|| config.getAccessKeyId().isBlank()
|
||||
|| config.getSignature() == null
|
||||
|| config.getSignature().isBlank()
|
||||
|| config.getTemplateId() == null
|
||||
|| config.getTemplateId().isBlank()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// HMAC mode requires additional check for accessKeySecret
|
||||
if ("hmac".equalsIgnoreCase(config.getAuthMode())
|
||||
&& (config.getAccessKeySecret() == null || config.getAccessKeySecret().isBlank())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
/*
|
||||
* 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 lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import javax.xml.bind.DatatypeConverter;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* General encryption utility class
|
||||
*/
|
||||
@Slf4j
|
||||
public class CryptoUtils {
|
||||
|
||||
private static final Charset UTF8 = StandardCharsets.UTF_8;
|
||||
|
||||
private CryptoUtils() {}
|
||||
|
||||
/**
|
||||
* Calculate HMAC-SHA256 signature
|
||||
* @param key secret key
|
||||
* @param msg message to be signed
|
||||
* @return signed byte array
|
||||
*/
|
||||
public static byte[] hmac256(byte[] key, String msg) {
|
||||
try {
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
SecretKeySpec secretKeySpec = new SecretKeySpec(key, mac.getAlgorithm());
|
||||
mac.init(secretKeySpec);
|
||||
return mac.doFinal(msg.getBytes(UTF8));
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to calculate HMAC-SHA256: {}", e.getMessage());
|
||||
throw new RuntimeException("Failed to calculate HMAC-SHA256", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate SHA256 hash and convert to lowercase hexadecimal string
|
||||
* @param data data to be hashed
|
||||
* @return lowercase hexadecimal string
|
||||
*/
|
||||
public static String sha256Hex(String data) {
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
byte[] digest = md.digest(data.getBytes(UTF8));
|
||||
return DatatypeConverter.printHexBinary(digest).toLowerCase();
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to calculate SHA256: {}", e.getMessage());
|
||||
throw new RuntimeException("Failed to calculate SHA256", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate HMAC-SHA256 signature and convert to Base64 string
|
||||
* @param key secret key
|
||||
* @param data data to be signed
|
||||
* @return Base64 encoded signature string
|
||||
*/
|
||||
public static String hmacSha256Base64(String key, String data) {
|
||||
byte[] hmacResult = hmac256(key.getBytes(UTF8), data);
|
||||
return Base64.getEncoder().encodeToString(hmacResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate HMAC-SHA256 signature and convert to lowercase hexadecimal string
|
||||
* @param key secret key
|
||||
* @param data data to be signed
|
||||
* @return lowercase hexadecimal string
|
||||
*/
|
||||
public static String hmacSha256Hex(String key, String data) {
|
||||
return hmacSha256Hex(hmac256(key.getBytes(UTF8), data), data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate HMAC-SHA256 signature and convert to lowercase hexadecimal string
|
||||
* Note: This is necessary as, converting a String to byte[] and back may yield different results due to encoding differences or data loss.
|
||||
* @param key secret key in byte array
|
||||
* @param data data to be signed
|
||||
* @return lowercase hexadecimal string
|
||||
*/
|
||||
public static String hmacSha256Hex(byte[] key, String data) {
|
||||
byte[] hmacResult = hmac256(key, data);
|
||||
return DatatypeConverter.printHexBinary(hmacResult).toLowerCase();
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
# 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.
|
||||
|
||||
alerter.availability.recover = 可用性警報已解決,監視狀態現在正常
|
||||
alerter.alarm.recover = 警報解決通知
|
||||
alerter.notify.title = HertzBeat 警報通知
|
||||
alerter.notify.target = 監視目標
|
||||
alerter.notify.monitorId = 監視 ID
|
||||
alerter.notify.monitorName = 監視名稱
|
||||
alerter.notify.monitorHost = 監視主機
|
||||
alerter.notify.priority = 警報優先級
|
||||
alerter.notify.triggerTime = 警報觸發時間
|
||||
alerter.notify.restoreTime = 警報恢復時間
|
||||
alerter.notify.times = 警報觸發次數
|
||||
alerter.notify.tags = 警報標籤
|
||||
alerter.notify.content = 警報內容
|
||||
alerter.notify.console = 控制台登錄
|
||||
alerter.priority.0 = 緊急警報
|
||||
alerter.priority.1 = 嚴重警報
|
||||
alerter.priority.2 = 警告警報
|
||||
+12
-1
@@ -28,6 +28,7 @@ import org.apache.hertzbeat.common.entity.alerter.AlertDefineMonitorBind;
|
||||
import org.apache.hertzbeat.common.entity.manager.Monitor;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
@@ -42,6 +43,7 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
/**
|
||||
* Test case for {@link AlertDefineController}
|
||||
*/
|
||||
@Disabled
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class AlertDefineControllerTest {
|
||||
|
||||
@@ -64,7 +66,6 @@ class AlertDefineControllerTest {
|
||||
|
||||
this.alertDefine = AlertDefine.builder()
|
||||
.id(1L)
|
||||
.name("alertDefine")
|
||||
.expr("1 > 0")
|
||||
.times(1)
|
||||
.template("template")
|
||||
@@ -137,4 +138,14 @@ class AlertDefineControllerTest {
|
||||
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyAlertDefineMonitorsBind() throws Exception {
|
||||
mockMvc.perform(MockMvcRequestBuilders.post("/api/alert/define/" + this.alertDefine.getId() + "/monitors")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(JsonUtil.toJson(this.alertDefineMonitorBinds)))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
|
||||
.andReturn();
|
||||
}
|
||||
}
|
||||
|
||||
+24
-16
@@ -19,6 +19,7 @@ package org.apache.hertzbeat.alert.controller;
|
||||
|
||||
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;
|
||||
@@ -30,6 +31,7 @@ import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
@@ -48,6 +50,7 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
* Test case for {@link AlertDefinesController}
|
||||
* Test whether the data mocked at the mock is correct, and test whether the format of the returned data is correct
|
||||
*/
|
||||
@Disabled
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class AlertDefinesControllerTest {
|
||||
|
||||
@@ -90,7 +93,8 @@ class AlertDefinesControllerTest {
|
||||
pageRequest = PageRequest.of((Integer) content.get("pageIndex"), (Integer) content.get("pageSize"), sortExp);
|
||||
}
|
||||
|
||||
@Test
|
||||
// @Test
|
||||
// todo: fix this test
|
||||
void getAlertDefines() throws Exception {
|
||||
|
||||
// Test the correctness of the mock
|
||||
@@ -108,27 +112,31 @@ class AlertDefinesControllerTest {
|
||||
// }
|
||||
// }))).thenReturn(new PageImpl<AlertDefine>(new ArrayList<AlertDefine>()));
|
||||
AlertDefine define = AlertDefine.builder().id(9L).expr("x").times(1).build();
|
||||
PageRequest pageRequest = PageRequest.of(pageIndex, pageSize, Sort.by(Sort.Order.asc(sort)));
|
||||
Mockito.when(alertDefineService.getAlertDefines(
|
||||
Mockito.eq(ids),
|
||||
Mockito.isNull(),
|
||||
Mockito.eq(sort),
|
||||
Mockito.eq(order),
|
||||
Mockito.eq(pageIndex),
|
||||
Mockito.eq(pageSize)
|
||||
)).thenReturn(new PageImpl<>(Collections.singletonList(define), pageRequest, 1));
|
||||
Mockito.when(alertDefineService.getAlertDefines(null, null, "id", "desc", 1, 10)).thenReturn(new PageImpl<>(Collections.singletonList(define)));
|
||||
|
||||
mockMvc.perform(MockMvcRequestBuilders.get("/api/alert/defines")
|
||||
mockMvc.perform(MockMvcRequestBuilders.get(
|
||||
"/api/alert/defines")
|
||||
.param("ids", ids.toString().substring(1, ids.toString().length() - 1))
|
||||
.param("priority", priority.toString())
|
||||
.param("sort", sort)
|
||||
.param("order", order)
|
||||
.param("pageIndex", String.valueOf(pageIndex))
|
||||
.param("pageSize", String.valueOf(pageSize)))
|
||||
.param("pageIndex", pageIndex.toString())
|
||||
.param("pageSize", pageSize.toString()))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
|
||||
.andExpect(jsonPath("$.data.content[0].id").value(9))
|
||||
.andExpect(jsonPath("$.data.content[0].expr").value("x"))
|
||||
.andExpect(jsonPath("$.data.content[0].times").value(1))
|
||||
.andExpect(jsonPath("$.data.content").value(new ArrayList<>()))
|
||||
.andExpect(jsonPath("$.data.pageable").value("INSTANCE"))
|
||||
.andExpect(jsonPath("$.data.totalPages").value(1))
|
||||
.andExpect(jsonPath("$.data.totalElements").value(0))
|
||||
.andExpect(jsonPath("$.data.last").value(true))
|
||||
.andExpect(jsonPath("$.data.number").value(0))
|
||||
.andExpect(jsonPath("$.data.size").value(0))
|
||||
.andExpect(jsonPath("$.data.first").value(true))
|
||||
.andExpect(jsonPath("$.data.numberOfElements").value(0))
|
||||
.andExpect(jsonPath("$.data.empty").value(true))
|
||||
.andExpect(jsonPath("$.data.sort.empty").value(true))
|
||||
.andExpect(jsonPath("$.data.sort.sorted").value(false))
|
||||
.andExpect(jsonPath("$.data.sort.unsorted").value(true))
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
|
||||
+8
-6
@@ -27,12 +27,12 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup;
|
||||
|
||||
import org.apache.hertzbeat.alert.service.AlertGroupConvergeService;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.alerter.AlertGroupConverge;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
@@ -44,6 +44,7 @@ import org.springframework.test.web.servlet.MockMvc;
|
||||
/**
|
||||
* test case for {@link AlertGroupConvergeController}
|
||||
*/
|
||||
@Disabled
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class AlertGroupConvergeControllerTest {
|
||||
|
||||
@@ -78,8 +79,8 @@ public class AlertGroupConvergeControllerTest {
|
||||
|
||||
mockMvc.perform(post("/api/alert/group")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(JsonUtil.toJson(alertGroupConverge)))
|
||||
.andExpect(status().isOk())
|
||||
.content(JsonUtil.toJson(alertGroupConverge))
|
||||
).andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
|
||||
.andExpect(jsonPath("$.msg").value("Add success"));
|
||||
}
|
||||
@@ -92,8 +93,8 @@ public class AlertGroupConvergeControllerTest {
|
||||
|
||||
mockMvc.perform(put("/api/alert/group")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(JsonUtil.toJson(alertGroupConverge)))
|
||||
.andExpect(status().isOk())
|
||||
.content(JsonUtil.toJson(alertGroupConverge))
|
||||
).andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
|
||||
.andExpect(jsonPath("$.msg").value("Modify success"));
|
||||
}
|
||||
@@ -118,6 +119,7 @@ public class AlertGroupConvergeControllerTest {
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value((int) CommonConstants.MONITOR_NOT_EXIST_CODE))
|
||||
.andExpect(jsonPath("$.msg").value("Alert Group Converge not exist."));
|
||||
.andExpect(jsonPath("$.msg").value("AlertGroupConverge not exist."));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+4
-2
@@ -33,6 +33,7 @@ import org.apache.hertzbeat.alert.service.AlertGroupConvergeService;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.alerter.AlertGroupConverge;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
@@ -51,6 +52,7 @@ import org.springframework.test.web.servlet.MockMvc;
|
||||
*/
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@Disabled
|
||||
class AlertGroupConvergesControllerTest {
|
||||
|
||||
private MockMvc mockMvc;
|
||||
@@ -99,7 +101,6 @@ class AlertGroupConvergesControllerTest {
|
||||
.param("order", "desc")
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
|
||||
.andExpect(jsonPath("$.data.content[0].id").value(1))
|
||||
.andExpect(jsonPath("$.data.content[0].name").value("Converge1"))
|
||||
.andExpect(jsonPath("$.data.content[1].id").value(2))
|
||||
@@ -117,4 +118,5 @@ class AlertGroupConvergesControllerTest {
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-133
@@ -1,133 +0,0 @@
|
||||
/*
|
||||
* 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.controller;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup;
|
||||
|
||||
import org.apache.hertzbeat.alert.service.AlertInhibitService;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.alerter.AlertInhibit;
|
||||
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 org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* test case for {@link AlertInhibitControllerTest}
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class AlertInhibitControllerTest {
|
||||
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Mock
|
||||
private AlertInhibitService alertInhibitService;
|
||||
|
||||
@InjectMocks
|
||||
private AlertInhibitController alertInhibitController;
|
||||
|
||||
private AlertInhibit alertInhibit;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
this.mockMvc = standaloneSetup(alertInhibitController).build();
|
||||
|
||||
HashMap<String, String> sourceLabels = new HashMap<>();
|
||||
HashMap<String, String> targetLabels = new HashMap<>();
|
||||
|
||||
alertInhibit = AlertInhibit.builder()
|
||||
.id(1L)
|
||||
.name("test")
|
||||
.sourceLabels(sourceLabels)
|
||||
.targetLabels(targetLabels)
|
||||
.creator("test")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddNewAlertInhibit() throws Exception {
|
||||
|
||||
doNothing().when(alertInhibitService).validate(any(AlertInhibit.class), eq(false));
|
||||
doNothing().when(alertInhibitService).addAlertInhibit(any(AlertInhibit.class));
|
||||
|
||||
mockMvc.perform(post("/api/alert/inhibit")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(JsonUtil.toJson(alertInhibit)))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
|
||||
.andExpect(jsonPath("$.msg").value("Add success"));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void testModifyAlertInhibit() throws Exception {
|
||||
|
||||
doNothing().when(alertInhibitService).validate(any(AlertInhibit.class), eq(true));
|
||||
doNothing().when(alertInhibitService).modifyAlertInhibit(any(AlertInhibit.class));
|
||||
|
||||
mockMvc.perform(put("/api/alert/inhibit")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(JsonUtil.toJson(alertInhibit)))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
|
||||
.andExpect(jsonPath("$.msg").value("Modify success"));
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetAlertInhibitExists() throws Exception {
|
||||
|
||||
when(alertInhibitService.getAlertInhibit(1L)).thenReturn(alertInhibit);
|
||||
|
||||
mockMvc.perform(get("/api/alert/inhibit/{id}", 1L)
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.id").value(alertInhibit.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetAlertInhibitNotExists() throws Exception {
|
||||
|
||||
when(alertInhibitService.getAlertInhibit(1L)).thenReturn(null);
|
||||
|
||||
mockMvc.perform(get("/api/alert/inhibit/{id}", 1L)
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value((int) CommonConstants.MONITOR_NOT_EXIST_CODE))
|
||||
.andExpect(jsonPath("$.msg").value("AlertInhibit not exist."));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+23
-113
@@ -17,6 +17,7 @@
|
||||
|
||||
package org.apache.hertzbeat.alert.controller;
|
||||
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
@@ -43,10 +44,6 @@ import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||
@@ -171,38 +168,12 @@ class NoticeConfigControllerTest {
|
||||
|
||||
@Test
|
||||
void getReceivers() throws Exception {
|
||||
NoticeReceiver receiver1 = new NoticeReceiver();
|
||||
receiver1.setId(1L);
|
||||
receiver1.setName("Receiver1");
|
||||
|
||||
NoticeReceiver receiver2 = new NoticeReceiver();
|
||||
receiver2.setId(2L);
|
||||
receiver2.setName("Receiver2");
|
||||
|
||||
Page<NoticeReceiver> receiverPage = new PageImpl<>(
|
||||
Arrays.asList(receiver1, receiver2),
|
||||
PageRequest.of(0, 8, Sort.by("id").descending()),
|
||||
2
|
||||
);
|
||||
|
||||
when(noticeConfigService.getNoticeReceivers("Receiver", 0, 8)).thenReturn(receiverPage);
|
||||
|
||||
this.mockMvc.perform(MockMvcRequestBuilders.get("/api/notice/receivers")
|
||||
.param("name", "Receiver")
|
||||
.param("pageIndex", "0")
|
||||
.param("pageSize", "8")
|
||||
.param("sort", "id")
|
||||
.param("order", "desc")
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
//Mockito.when(noticeConfigService.getNoticeReceivers())
|
||||
this.mockMvc.perform(MockMvcRequestBuilders.get("/api/notice/receivers?name={name}", "tom"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.content[0].id").value(1))
|
||||
.andExpect(jsonPath("$.data.content[0].name").value("Receiver1"))
|
||||
.andExpect(jsonPath("$.data.content[1].id").value(2))
|
||||
.andExpect(jsonPath("$.data.content[1].name").value("Receiver2"))
|
||||
.andExpect(jsonPath("$.data.totalElements").value(2))
|
||||
.andExpect(jsonPath("$.data.totalPages").value(1))
|
||||
.andExpect(jsonPath("$.data.size").value(8))
|
||||
.andExpect(jsonPath("$.data.number").value(0));
|
||||
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -274,38 +245,15 @@ class NoticeConfigControllerTest {
|
||||
|
||||
@Test
|
||||
void getRules() throws Exception {
|
||||
NoticeRule rule1 = new NoticeRule();
|
||||
rule1.setId(1L);
|
||||
rule1.setName("Rule1");
|
||||
|
||||
NoticeRule rule2 = new NoticeRule();
|
||||
rule2.setId(2L);
|
||||
rule2.setName("Rule2");
|
||||
|
||||
Page<NoticeRule> rulePage = new PageImpl<>(
|
||||
Arrays.asList(rule1, rule2),
|
||||
PageRequest.of(0, 8, Sort.by("id").descending()),
|
||||
2
|
||||
);
|
||||
|
||||
when(noticeConfigService.getNoticeRules("Rule", 0, 8)).thenReturn(rulePage);
|
||||
|
||||
this.mockMvc.perform(MockMvcRequestBuilders.get("/api/notice/rules")
|
||||
.param("name", "Rule")
|
||||
.param("pageIndex", "0")
|
||||
.param("pageSize", "8")
|
||||
.param("sort", "id")
|
||||
.param("order", "desc")
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
this.mockMvc.perform(MockMvcRequestBuilders.get("/api/notice/rules"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.content[0].id").value(1))
|
||||
.andExpect(jsonPath("$.data.content[0].name").value("Rule1"))
|
||||
.andExpect(jsonPath("$.data.content[1].id").value(2))
|
||||
.andExpect(jsonPath("$.data.content[1].name").value("Rule2"))
|
||||
.andExpect(jsonPath("$.data.totalElements").value(2))
|
||||
.andExpect(jsonPath("$.data.totalPages").value(1))
|
||||
.andExpect(jsonPath("$.data.size").value(8))
|
||||
.andExpect(jsonPath("$.data.number").value(0));
|
||||
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
|
||||
.andReturn();
|
||||
|
||||
this.mockMvc.perform(MockMvcRequestBuilders.get("/api/notice/rules?name={name}", "tom"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -414,40 +362,22 @@ class NoticeConfigControllerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void getTemplates() throws Exception {
|
||||
void testGetTemplates() throws Exception {
|
||||
// Mock the service response
|
||||
NoticeTemplate template1 = new NoticeTemplate();
|
||||
template1.setId(1L);
|
||||
template1.setName("Template1");
|
||||
|
||||
NoticeTemplate template2 = new NoticeTemplate();
|
||||
template2.setId(2L);
|
||||
template2.setName("Template2");
|
||||
List<NoticeTemplate> templates = Arrays.asList(template1, template2);
|
||||
when(noticeConfigService.getNoticeTemplates(any())).thenReturn(templates);
|
||||
|
||||
Page<NoticeTemplate> templatePage = new PageImpl<>(
|
||||
Arrays.asList(template1, template2),
|
||||
PageRequest.of(0, 8, Sort.by("id").descending()),
|
||||
2
|
||||
);
|
||||
|
||||
when(noticeConfigService.getNoticeTemplates("Template", true, 0, 8)).thenReturn(templatePage);
|
||||
|
||||
this.mockMvc.perform(MockMvcRequestBuilders.get("/api/notice/templates")
|
||||
.param("name", "Template")
|
||||
.param("preset", "true")
|
||||
.param("pageIndex", "0")
|
||||
.param("pageSize", "8")
|
||||
.param("sort", "id")
|
||||
.param("order", "desc")
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
// Perform the GET request and verify the response
|
||||
this.mockMvc.perform(get("/api/notice/templates")
|
||||
.param("name", "Template"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.content[0].id").value(1))
|
||||
.andExpect(jsonPath("$.data.content[0].name").value("Template1"))
|
||||
.andExpect(jsonPath("$.data.content[1].id").value(2))
|
||||
.andExpect(jsonPath("$.data.content[1].name").value("Template2"))
|
||||
.andExpect(jsonPath("$.data.totalElements").value(2))
|
||||
.andExpect(jsonPath("$.data.totalPages").value(1))
|
||||
.andExpect(jsonPath("$.data.size").value(8))
|
||||
.andExpect(jsonPath("$.data.number").value(0));
|
||||
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
|
||||
.andExpect(jsonPath("$.data[0].name").value("Template1"))
|
||||
.andExpect(jsonPath("$.data[1].name").value("Template2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -484,25 +414,5 @@ class NoticeConfigControllerTest {
|
||||
verify(noticeConfigService, times(1)).sendTestMsg(noticeReceiver);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getAllTemplates() throws Exception {
|
||||
List<NoticeTemplate> templates = Arrays.asList(new NoticeTemplate(), new NoticeTemplate());
|
||||
when(noticeConfigService.getAllNoticeTemplates()).thenReturn(templates);
|
||||
|
||||
this.mockMvc.perform(MockMvcRequestBuilders.get("/api/notice/templates/all"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getAllReceivers() throws Exception {
|
||||
List<NoticeReceiver> receivers = Arrays.asList(new NoticeReceiver(), new NoticeReceiver());
|
||||
when(noticeConfigService.getAllNoticeReceivers()).thenReturn(receivers);
|
||||
|
||||
this.mockMvc.perform(MockMvcRequestBuilders.get("/api/notice/receivers/all"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
|
||||
.andReturn();
|
||||
}
|
||||
}
|
||||
|
||||
+1
-6
@@ -27,7 +27,6 @@ import static org.mockito.Mockito.when;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import org.apache.hertzbeat.alert.AlerterWorkerPool;
|
||||
import org.apache.hertzbeat.alert.config.AlertSseManager;
|
||||
import org.apache.hertzbeat.alert.service.NoticeConfigService;
|
||||
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
|
||||
@@ -61,9 +60,6 @@ class AlertNoticeDispatchTest {
|
||||
@Mock
|
||||
private AlertNotifyHandler alertNotifyHandler;
|
||||
|
||||
@Mock
|
||||
private AlertSseManager emitterManager;
|
||||
|
||||
private AlertNoticeDispatch alertNoticeDispatch;
|
||||
|
||||
private static final int DISPATCH_THREADS = 3;
|
||||
@@ -81,8 +77,7 @@ class AlertNoticeDispatchTest {
|
||||
noticeConfigService,
|
||||
alertStoreHandler,
|
||||
alertNotifyHandlerList,
|
||||
pluginRunner,
|
||||
emitterManager
|
||||
pluginRunner
|
||||
);
|
||||
|
||||
receiver = NoticeReceiver.builder()
|
||||
|
||||
+10
-19
@@ -27,6 +27,7 @@ import org.apache.hertzbeat.alert.dao.SingleAlertDao;
|
||||
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
|
||||
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
@@ -38,6 +39,7 @@ import java.util.List;
|
||||
/**
|
||||
* Test case for {@link DbAlertStoreHandlerImpl}
|
||||
*/
|
||||
@Disabled
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class DbAlertStoreHandlerImplTest {
|
||||
|
||||
@@ -73,34 +75,27 @@ class DbAlertStoreHandlerImplTest {
|
||||
public void testStoreNewAlert() {
|
||||
String groupKey = "test-group";
|
||||
groupAlert.setGroupKey(groupKey);
|
||||
|
||||
|
||||
when(groupAlertDao.findByGroupKey(groupKey)).thenReturn(null);
|
||||
|
||||
SingleAlert savedSingleAlert = new SingleAlert();
|
||||
when(singleAlertDao.save(any(SingleAlert.class))).thenReturn(savedSingleAlert);
|
||||
|
||||
GroupAlert savedGroupAlert = new GroupAlert();
|
||||
when(groupAlertDao.save(any(GroupAlert.class))).thenReturn(savedGroupAlert);
|
||||
|
||||
|
||||
dbAlertStoreHandler.store(groupAlert);
|
||||
|
||||
|
||||
verify(singleAlertDao).save(any(SingleAlert.class));
|
||||
verify(groupAlertDao).save(groupAlert);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testStoreExistingAlert() {
|
||||
String groupKey = "test-group";
|
||||
String fingerprint = "test-fingerprint";
|
||||
|
||||
|
||||
groupAlert.setGroupKey(groupKey);
|
||||
singleAlert.setFingerprint(fingerprint);
|
||||
|
||||
|
||||
GroupAlert existingGroup = new GroupAlert();
|
||||
existingGroup.setId(1L);
|
||||
when(groupAlertDao.findByGroupKey(groupKey)).thenReturn(existingGroup);
|
||||
|
||||
|
||||
SingleAlert existingAlert = new SingleAlert();
|
||||
existingAlert.setId(1L);
|
||||
existingAlert.setStatus("firing");
|
||||
@@ -108,15 +103,11 @@ class DbAlertStoreHandlerImplTest {
|
||||
existingAlert.setActiveAt(2000L);
|
||||
existingAlert.setTriggerTimes(1);
|
||||
when(singleAlertDao.findByFingerprint(fingerprint)).thenReturn(existingAlert);
|
||||
|
||||
when(singleAlertDao.save(any(SingleAlert.class))).thenReturn(existingAlert);
|
||||
when(groupAlertDao.save(any(GroupAlert.class))).thenReturn(existingGroup);
|
||||
|
||||
|
||||
dbAlertStoreHandler.store(groupAlert);
|
||||
|
||||
|
||||
verify(singleAlertDao).save(any(SingleAlert.class));
|
||||
verify(groupAlertDao).save(groupAlert);
|
||||
assertEquals(1L, groupAlert.getId());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -99,7 +99,7 @@ class EmailAlertNotifyHandlerImplTest {
|
||||
template.setName("test-template");
|
||||
template.setContent("test content");
|
||||
|
||||
// Set up email server configuration
|
||||
// 设置邮件服务器配置
|
||||
MailServerConfig mailServerConfig = new MailServerConfig();
|
||||
mailServerConfig.setEmailHost("smtp.example.com");
|
||||
mailServerConfig.setEmailPort(587);
|
||||
|
||||
+2
-2
@@ -83,8 +83,8 @@ class AlarmInhibitReduceTest {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
when(alertInhibitDao.findAlertInhibitsByEnableIsTrue())
|
||||
.thenReturn(Collections.emptyList());
|
||||
|
||||
// Correctly set up AlerterProperties mock
|
||||
|
||||
// 正确设置 AlerterProperties mock
|
||||
AlerterProperties.InhibitProperties inhibitProperties = new AlerterProperties.InhibitProperties();
|
||||
inhibitProperties.setTtl(60000);
|
||||
when(alerterProperties.getInhibit()).thenReturn(inhibitProperties);
|
||||
|
||||
+32
-11
@@ -30,17 +30,18 @@ import java.util.Map;
|
||||
|
||||
import org.apache.hertzbeat.alert.dao.AlertSilenceDao;
|
||||
import org.apache.hertzbeat.alert.notice.AlertNoticeDispatch;
|
||||
import org.apache.hertzbeat.common.cache.CacheFactory;
|
||||
import org.apache.hertzbeat.common.entity.alerter.AlertSilence;
|
||||
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
/**
|
||||
* Test for {@link AlarmSilenceReduce}
|
||||
* Test for AlarmSilenceReduce
|
||||
*/
|
||||
@Disabled
|
||||
class AlarmSilenceReduceTest {
|
||||
|
||||
@Mock
|
||||
@@ -54,23 +55,21 @@ class AlarmSilenceReduceTest {
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
CacheFactory.clearAlertSilenceCache();
|
||||
when(alertSilenceDao.findAll()).thenReturn(Collections.emptyList());
|
||||
alarmSilenceReduce = new AlarmSilenceReduce(alertSilenceDao, alertNoticeDispatch);
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenNoSilenceRules_shouldForwardAlert() {
|
||||
when(alertSilenceDao.findAlertSilencesByEnableTrue()).thenReturn(Collections.emptyList());
|
||||
|
||||
GroupAlert alert = createGroupAlert("firing", createLabels("service", "web"));
|
||||
|
||||
alarmSilenceReduce.silenceAlarm(alert);
|
||||
|
||||
|
||||
verify(alertNoticeDispatch).dispatchAlarm(alert);
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenMatchingSilenceRule_shouldNotForwardAlert() {
|
||||
|
||||
// Create silence rule
|
||||
AlertSilence silenceRule = AlertSilence.builder()
|
||||
.enable(true)
|
||||
@@ -82,7 +81,7 @@ class AlarmSilenceReduceTest {
|
||||
.times(0)
|
||||
.build();
|
||||
|
||||
when(alertSilenceDao.findAlertSilencesByEnableTrue()).thenReturn(Collections.singletonList(silenceRule));
|
||||
when(alertSilenceDao.findAll()).thenReturn(Collections.singletonList(silenceRule));
|
||||
when(alertSilenceDao.save(any(AlertSilence.class))).thenReturn(silenceRule);
|
||||
|
||||
GroupAlert alert = createGroupAlert("firing", createLabels("service", "web"));
|
||||
@@ -108,9 +107,9 @@ class AlarmSilenceReduceTest {
|
||||
.times(0)
|
||||
.build();
|
||||
|
||||
when(alertSilenceDao.findAlertSilencesByEnableTrue()).thenReturn(Collections.singletonList(silenceRule));
|
||||
when(alertSilenceDao.findAll()).thenReturn(Collections.singletonList(silenceRule));
|
||||
when(alertSilenceDao.save(any(AlertSilence.class))).thenReturn(silenceRule);
|
||||
|
||||
|
||||
GroupAlert alert = createGroupAlert("firing", createLabels("service", "web"));
|
||||
|
||||
alarmSilenceReduce.silenceAlarm(alert);
|
||||
@@ -131,7 +130,29 @@ class AlarmSilenceReduceTest {
|
||||
.times(0)
|
||||
.build();
|
||||
|
||||
when(alertSilenceDao.findAlertSilencesByEnableTrue()).thenReturn(Collections.singletonList(silenceRule));
|
||||
when(alertSilenceDao.findAll()).thenReturn(Collections.singletonList(silenceRule));
|
||||
|
||||
GroupAlert alert = createGroupAlert("firing", createLabels("service", "web"));
|
||||
|
||||
alarmSilenceReduce.silenceAlarm(alert);
|
||||
|
||||
verify(alertNoticeDispatch).dispatchAlarm(alert);
|
||||
verify(alertSilenceDao, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenSilenceRuleDisabled_shouldForwardAlert() {
|
||||
AlertSilence silenceRule = AlertSilence.builder()
|
||||
.enable(false)
|
||||
.matchAll(false)
|
||||
.type((byte) 0)
|
||||
.labels(createLabels("service", "web"))
|
||||
.periodStart(LocalDateTime.now().minusHours(1).atZone(ZoneId.systemDefault()))
|
||||
.periodEnd(LocalDateTime.now().plusHours(1).atZone(ZoneId.systemDefault()))
|
||||
.times(0)
|
||||
.build();
|
||||
|
||||
when(alertSilenceDao.findAll()).thenReturn(Collections.singletonList(silenceRule));
|
||||
|
||||
GroupAlert alert = createGroupAlert("firing", createLabels("service", "web"));
|
||||
|
||||
|
||||
+25
-35
@@ -24,18 +24,16 @@ import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.hertzbeat.alert.dto.AlertDefineDTO;
|
||||
import org.apache.hertzbeat.alert.dto.ExportAlertDefineDTO;
|
||||
import org.apache.hertzbeat.alert.service.impl.AlertDefineExcelImExportServiceImpl;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.apache.hertzbeat.common.util.export.ExcelExportUtils;
|
||||
import org.apache.poi.ss.usermodel.Row;
|
||||
import org.apache.poi.ss.usermodel.Sheet;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
import org.apache.poi.ss.usermodel.WorkbookFactory;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
@@ -44,6 +42,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||
/**
|
||||
* test case for {@link AlertDefineExcelImExportServiceImpl}
|
||||
*/
|
||||
@Disabled
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class AlertDefineExcelImExportServiceTest {
|
||||
|
||||
@@ -62,13 +61,15 @@ public class AlertDefineExcelImExportServiceTest {
|
||||
Row row = initialSheet.createRow(1);
|
||||
row.createCell(0).setCellValue("app1");
|
||||
row.createCell(1).setCellValue("metric1");
|
||||
row.createCell(2).setCellValue("expr1");
|
||||
row.createCell(3).setCellValue(10);
|
||||
row.createCell(4).setCellValue(1);
|
||||
row.createCell(5).setCellValue(JsonUtil.toJson(Map.of("key", "value")));
|
||||
row.createCell(6).setCellValue(JsonUtil.toJson(Map.of("key", "value")));
|
||||
row.createCell(7).setCellValue("template1");
|
||||
row.createCell(2).setCellValue("field1");
|
||||
row.createCell(3).setCellValue(true);
|
||||
row.createCell(4).setCellValue("expr1");
|
||||
row.createCell(5).setCellValue(1);
|
||||
row.createCell(6).setCellValue(10);
|
||||
row.createCell(7).setCellValue("[{\"name\":\"tag1\",\"value\":\"value1\"}]");
|
||||
row.createCell(8).setCellValue(true);
|
||||
row.createCell(9).setCellValue(true);
|
||||
row.createCell(10).setCellValue("template1");
|
||||
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(toByteArray(initialWorkbook));
|
||||
|
||||
@@ -87,12 +88,9 @@ public class AlertDefineExcelImExportServiceTest {
|
||||
assertEquals("app1", alertDefineDTO.getName());
|
||||
assertEquals("metric1", alertDefineDTO.getType());
|
||||
assertEquals("expr1", alertDefineDTO.getExpr());
|
||||
assertEquals(10, alertDefineDTO.getPeriod());
|
||||
assertEquals(1, alertDefineDTO.getTimes());
|
||||
assertEquals(Map.of("key", "value"), alertDefineDTO.getLabels());
|
||||
assertEquals(Map.of("key", "value"), alertDefineDTO.getAnnotations());
|
||||
assertEquals("template1", alertDefineDTO.getTemplate());
|
||||
assertEquals(10, alertDefineDTO.getTimes());
|
||||
assertTrue(alertDefineDTO.getEnable());
|
||||
assertEquals("template1", alertDefineDTO.getTemplate());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,12 +103,9 @@ public class AlertDefineExcelImExportServiceTest {
|
||||
alertDefineDTO.setName("app1");
|
||||
alertDefineDTO.setType("metric1");
|
||||
alertDefineDTO.setExpr("expr1");
|
||||
alertDefineDTO.setPeriod(10);
|
||||
alertDefineDTO.setTimes(1);
|
||||
alertDefineDTO.setLabels(Map.of("key", "value"));
|
||||
alertDefineDTO.setAnnotations(Map.of("key", "value"));
|
||||
alertDefineDTO.setTemplate("template1");
|
||||
alertDefineDTO.setTimes(10);
|
||||
alertDefineDTO.setEnable(true);
|
||||
alertDefineDTO.setTemplate("template1");
|
||||
exportAlertDefineDTO.setAlertDefine(alertDefineDTO);
|
||||
exportAlertDefineList.add(exportAlertDefineDTO);
|
||||
|
||||
@@ -120,26 +115,21 @@ public class AlertDefineExcelImExportServiceTest {
|
||||
try (Workbook resultWorkbook = WorkbookFactory.create(new ByteArrayInputStream(outputStream.toByteArray()))) {
|
||||
Sheet resultSheet = resultWorkbook.getSheetAt(0);
|
||||
Row headerRow = resultSheet.getRow(0);
|
||||
assertEquals("Name", headerRow.getCell(0).getStringCellValue());
|
||||
assertEquals("Type", headerRow.getCell(1).getStringCellValue());
|
||||
assertEquals("Expr", headerRow.getCell(2).getStringCellValue());
|
||||
assertEquals("Period", headerRow.getCell(3).getStringCellValue());
|
||||
assertEquals("Times", headerRow.getCell(4).getStringCellValue());
|
||||
assertEquals("Labels", headerRow.getCell(5).getStringCellValue());
|
||||
assertEquals("Annotations", headerRow.getCell(6).getStringCellValue());
|
||||
assertEquals("Template", headerRow.getCell(7).getStringCellValue());
|
||||
assertEquals("Enable", headerRow.getCell(8).getStringCellValue());
|
||||
assertEquals("app", headerRow.getCell(0).getStringCellValue());
|
||||
assertEquals("metric", headerRow.getCell(1).getStringCellValue());
|
||||
|
||||
Row dataRow = resultSheet.getRow(1);
|
||||
assertEquals("app1", dataRow.getCell(0).getStringCellValue());
|
||||
assertEquals("metric1", dataRow.getCell(1).getStringCellValue());
|
||||
assertEquals("expr1", dataRow.getCell(2).getStringCellValue());
|
||||
assertEquals(10, (int) dataRow.getCell(3).getNumericCellValue());
|
||||
assertEquals(1, (int) dataRow.getCell(4).getNumericCellValue());
|
||||
assertEquals(JsonUtil.toJson(Map.of("key", "value")), dataRow.getCell(5).getStringCellValue());
|
||||
assertEquals(JsonUtil.toJson(Map.of("key", "value")), dataRow.getCell(6).getStringCellValue());
|
||||
assertEquals("template1", dataRow.getCell(7).getStringCellValue());
|
||||
assertEquals("field1", dataRow.getCell(2).getStringCellValue());
|
||||
assertTrue(dataRow.getCell(3).getBooleanCellValue());
|
||||
assertEquals("expr1", dataRow.getCell(4).getStringCellValue());
|
||||
assertEquals(1, (int) dataRow.getCell(5).getNumericCellValue());
|
||||
assertEquals(10, (int) dataRow.getCell(6).getNumericCellValue());
|
||||
assertEquals("[{\"name\":\"tag1\",\"value\":\"value1\"}]", dataRow.getCell(7).getStringCellValue());
|
||||
assertTrue(dataRow.getCell(8).getBooleanCellValue());
|
||||
assertTrue(dataRow.getCell(9).getBooleanCellValue());
|
||||
assertEquals("template1", dataRow.getCell(10).getStringCellValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -152,4 +142,4 @@ public class AlertDefineExcelImExportServiceTest {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+1
-2
@@ -33,7 +33,6 @@ import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import org.apache.hertzbeat.alert.dto.AlertDefineDTO;
|
||||
import org.apache.hertzbeat.alert.dto.ExportAlertDefineDTO;
|
||||
@@ -69,7 +68,7 @@ class AlertDefineJsonImExportServiceTest {
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
|
||||
inputStream = new ByteArrayInputStream(JSON_DATA.getBytes(StandardCharsets.UTF_8));
|
||||
inputStream = new ByteArrayInputStream(JSON_DATA.getBytes());
|
||||
|
||||
AlertDefineDTO alertDefine = new AlertDefineDTO();
|
||||
alertDefine.setName("App1");
|
||||
|
||||
-5
@@ -30,7 +30,6 @@ import static org.mockito.Mockito.when;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.apache.hertzbeat.alert.calculate.PeriodicAlertRuleScheduler;
|
||||
import org.apache.hertzbeat.alert.dao.AlertDefineDao;
|
||||
import org.apache.hertzbeat.alert.service.impl.AlertDefineServiceImpl;
|
||||
import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
|
||||
@@ -55,9 +54,6 @@ class AlertDefineServiceTest {
|
||||
|
||||
@Mock
|
||||
private AlertDefineDao alertDefineDao;
|
||||
|
||||
@Mock
|
||||
private PeriodicAlertRuleScheduler periodicAlertRuleScheduler;
|
||||
|
||||
@Mock
|
||||
private List<AlertDefineImExportService> alertDefineImExportServiceList;
|
||||
@@ -68,7 +64,6 @@ class AlertDefineServiceTest {
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
ReflectionTestUtils.setField(this.alertDefineService, "alertDefineDao", alertDefineDao);
|
||||
ReflectionTestUtils.setField(this.alertDefineService, "periodicAlertRuleScheduler", periodicAlertRuleScheduler);
|
||||
|
||||
this.alertDefine = AlertDefine.builder()
|
||||
.id(1L)
|
||||
|
||||
+3
-1
@@ -39,6 +39,7 @@ import org.apache.hertzbeat.alert.dto.ExportAlertDefineDTO;
|
||||
import org.apache.hertzbeat.alert.service.impl.AlertDefineYamlImExportServiceImpl;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
@@ -47,7 +48,8 @@ import org.yaml.snakeyaml.Yaml;
|
||||
|
||||
/**
|
||||
* test case for {@link AlertDefineYamlImExportServiceImpl}
|
||||
*/
|
||||
*/
|
||||
@Disabled
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class AlertDefineYamlImExportServiceTest {
|
||||
|
||||
|
||||
+25
-35
@@ -17,27 +17,22 @@
|
||||
|
||||
package org.apache.hertzbeat.alert.service;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.apache.hertzbeat.alert.dao.AlertGroupConvergeDao;
|
||||
import org.apache.hertzbeat.alert.reduce.AlarmGroupReduce;
|
||||
import org.apache.hertzbeat.alert.service.impl.AlertGroupConvergeServiceImpl;
|
||||
import org.apache.hertzbeat.common.entity.alerter.AlertGroupConverge;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyList;
|
||||
import static org.mockito.ArgumentMatchers.anySet;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import org.apache.hertzbeat.alert.dao.AlertGroupConvergeDao;
|
||||
import org.apache.hertzbeat.alert.service.impl.AlertGroupConvergeServiceImpl;
|
||||
import org.apache.hertzbeat.common.entity.alerter.AlertGroupConverge;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
@@ -47,9 +42,10 @@ import org.springframework.data.jpa.domain.Specification;
|
||||
|
||||
/**
|
||||
* test case for {@link AlertGroupConvergeServiceImpl}
|
||||
*/
|
||||
*/
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@Disabled
|
||||
class AlertGroupConvergeServiceTest {
|
||||
|
||||
@Mock
|
||||
@@ -58,28 +54,22 @@ class AlertGroupConvergeServiceTest {
|
||||
@InjectMocks
|
||||
private AlertGroupConvergeServiceImpl alertGroupConvergeService;
|
||||
|
||||
@Mock
|
||||
private AlarmGroupReduce alarmGroupReduce;
|
||||
|
||||
@Test
|
||||
public void testAddAlertGroupConverge() {
|
||||
AlertGroupConverge alertGroupConverge = new AlertGroupConverge();
|
||||
doNothing().when(alarmGroupReduce).refreshGroupDefines(anyList());
|
||||
when(alertGroupConvergeDao.save(alertGroupConverge)).thenReturn(alertGroupConverge);
|
||||
alertGroupConvergeService.addAlertGroupConverge(alertGroupConverge);
|
||||
verify(alertGroupConvergeDao, times(1)).save(alertGroupConverge);
|
||||
verify(alarmGroupReduce, times(1)).refreshGroupDefines(anyList());
|
||||
}
|
||||
|
||||
AlertGroupConverge alertGroupConverge = new AlertGroupConverge();
|
||||
alertGroupConvergeService.addAlertGroupConverge(alertGroupConverge);
|
||||
|
||||
verify(alertGroupConvergeDao, times(1)).save(alertGroupConverge);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testModifyAlertGroupConverge() {
|
||||
|
||||
AlertGroupConverge alertGroupConverge = new AlertGroupConverge();
|
||||
doNothing().when(alarmGroupReduce).refreshGroupDefines(anyList());
|
||||
when(alertGroupConvergeDao.save(alertGroupConverge)).thenReturn(alertGroupConverge);
|
||||
alertGroupConvergeService.modifyAlertGroupConverge(alertGroupConverge);
|
||||
|
||||
verify(alertGroupConvergeDao, times(1)).save(alertGroupConverge);
|
||||
verify(alarmGroupReduce, times(1)).refreshGroupDefines(anyList());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -89,6 +79,7 @@ class AlertGroupConvergeServiceTest {
|
||||
AlertGroupConverge alertGroupConverge = new AlertGroupConverge();
|
||||
when(alertGroupConvergeDao.findById(convergeId)).thenReturn(Optional.of(alertGroupConverge));
|
||||
AlertGroupConverge result = alertGroupConvergeService.getAlertGroupConverge(convergeId);
|
||||
|
||||
verify(alertGroupConvergeDao, times(1)).findById(convergeId);
|
||||
assertEquals(alertGroupConverge, result);
|
||||
}
|
||||
@@ -96,11 +87,10 @@ class AlertGroupConvergeServiceTest {
|
||||
@Test
|
||||
public void testDeleteAlertGroupConverges() {
|
||||
|
||||
doNothing().when(alertGroupConvergeDao).deleteAlertGroupConvergesByIdIn(anySet());
|
||||
doNothing().when(alarmGroupReduce).refreshGroupDefines(anyList());
|
||||
alertGroupConvergeService.deleteAlertGroupConverges(new HashSet<>(Arrays.asList(1L, 2L)));
|
||||
verify(alertGroupConvergeDao, times(1)).deleteAlertGroupConvergesByIdIn(anySet());
|
||||
verify(alarmGroupReduce, times(1)).refreshGroupDefines(anyList());
|
||||
Set<Long> convergeIds = Set.of(1L, 2L, 3L);
|
||||
alertGroupConvergeService.deleteAlertGroupConverges(convergeIds);
|
||||
|
||||
verify(alertGroupConvergeDao, times(1)).deleteAlertGroupConvergesByIdIn(convergeIds);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+15
-21
@@ -17,23 +17,25 @@
|
||||
|
||||
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.assertEquals;
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import java.util.Collections;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.apache.hertzbeat.alert.dao.GroupAlertDao;
|
||||
import org.apache.hertzbeat.alert.dao.SingleAlertDao;
|
||||
import org.apache.hertzbeat.alert.dto.AlertSummary;
|
||||
import org.apache.hertzbeat.alert.reduce.AlarmCommonReduce;
|
||||
import org.apache.hertzbeat.alert.service.impl.AlertServiceImpl;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
@@ -42,8 +44,9 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
/**
|
||||
* Test case for {@link AlertService}
|
||||
*/
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@Disabled
|
||||
class AlertServiceTest {
|
||||
@Mock
|
||||
private GroupAlertDao groupAlertDao;
|
||||
@@ -80,23 +83,14 @@ class AlertServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetAlertsSummary() {
|
||||
SingleAlert alert = new SingleAlert();
|
||||
alert.setLabels(Collections.singletonMap(CommonConstants.LABEL_ALERT_SEVERITY, CommonConstants.ALERT_SEVERITY_CRITICAL));
|
||||
void getAlertsSummary() {
|
||||
List<SingleAlert> singleAlerts = new ArrayList<>();
|
||||
singleAlerts.add(SingleAlert.builder().status("firing")
|
||||
.labels(Map.of(CommonConstants.LABEL_ALERT_SEVERITY, "warning")).build());
|
||||
when(singleAlertDao.querySingleAlertsByStatus(any())).thenReturn(singleAlerts);
|
||||
when(singleAlertDao.count()).thenReturn(1L);
|
||||
|
||||
when(singleAlertDao.querySingleAlertsByStatus(CommonConstants.ALERT_STATUS_FIRING)).thenReturn(Collections.singletonList(alert));
|
||||
when(singleAlertDao.count()).thenReturn(10L);
|
||||
|
||||
AlertSummary summary = alertService.getAlertsSummary();
|
||||
|
||||
assertNotNull(summary);
|
||||
assertEquals(1, summary.getPriorityCriticalNum());
|
||||
assertEquals(0, summary.getPriorityEmergencyNum());
|
||||
assertEquals(0, summary.getPriorityWarningNum());
|
||||
assertEquals(10L, summary.getTotal());
|
||||
assertEquals(90.0f, summary.getRate());
|
||||
|
||||
verify(singleAlertDao, times(1)).querySingleAlertsByStatus(CommonConstants.ALERT_STATUS_FIRING);
|
||||
verify(singleAlertDao, times(1)).count();
|
||||
assertDoesNotThrow(() -> alertService.getAlertsSummary());
|
||||
assertNotNull(alertService.getAlertsSummary());
|
||||
}
|
||||
}
|
||||
|
||||
-69
@@ -1,69 +0,0 @@
|
||||
/*
|
||||
* 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.config.AwsSmsProperties;
|
||||
import org.apache.hertzbeat.alert.service.impl.AwsSmsClientImpl;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.CsvSource;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* Test case for {@link AwsSmsClientImpl}
|
||||
*/
|
||||
class AwsSmsClientImplTest {
|
||||
|
||||
|
||||
@Test
|
||||
void getType() {
|
||||
AwsSmsProperties awsSmsProperties = new AwsSmsProperties();
|
||||
awsSmsProperties.setAccessKeyId("accessKeyId");
|
||||
awsSmsProperties.setAccessKeySecret("accessKeySecret");
|
||||
awsSmsProperties.setRegion("region");
|
||||
AwsSmsClientImpl awsSmsClient = new AwsSmsClientImpl(awsSmsProperties);
|
||||
|
||||
assertEquals("aws", awsSmsClient.getType());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource({
|
||||
"accessKeyId, accessKeySecret, region, true",
|
||||
"accessKeyId, accessKeySecret, '', false",
|
||||
"accessKeyId, '', region, false",
|
||||
"accessKeyId, accessKeySecret, '', false",
|
||||
"accessKeyId, '', '', false",
|
||||
"'', accessKeySecret, region, false",
|
||||
"'', accessKeySecret, '', false",
|
||||
"'', '', region, false",
|
||||
"'', accessKeySecret, '', false",
|
||||
})
|
||||
void checkConfig(String accessKeyId, String accessKeySecret, String region, boolean expected) {
|
||||
AwsSmsProperties awsSmsProperties = new AwsSmsProperties();
|
||||
awsSmsProperties.setAccessKeyId(accessKeyId);
|
||||
awsSmsProperties.setAccessKeySecret(accessKeySecret);
|
||||
awsSmsProperties.setRegion(region);
|
||||
AwsSmsClientImpl awsSmsClient = new AwsSmsClientImpl(awsSmsProperties);
|
||||
|
||||
assertEquals(expected, awsSmsClient.checkConfig());
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
-547
@@ -1,547 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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 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 org.apache.hertzbeat.alert.service.impl.DataSourceServiceImpl;
|
||||
import org.apache.hertzbeat.warehouse.db.QueryExecutor;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
/**
|
||||
* test case for {@link DataSourceService}
|
||||
*/
|
||||
class DataSourceServiceTest {
|
||||
|
||||
private DataSourceServiceImpl dataSourceService;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
dataSourceService = new DataSourceServiceImpl();
|
||||
}
|
||||
|
||||
@Test
|
||||
void calculate1() {
|
||||
List<Map<String, Object>> prometheusData = 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(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__"));
|
||||
assertEquals(200.0, result.get(1).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void calculate2() {
|
||||
List<Map<String, Object>> prometheusData = 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(Mockito.anyString())).thenReturn(prometheusData);
|
||||
dataSourceService.setExecutors(List.of(mockExecutor));
|
||||
|
||||
List<Map<String, Object>> result = dataSourceService.calculate("promql", "node_cpu_seconds_total <= 100");
|
||||
assertEquals(2, result.size());
|
||||
assertEquals(100.0, result.get(0).get("__value__"));
|
||||
assertNull(result.get(1).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void calculate3() {
|
||||
List<Map<String, Object>> prometheusData = 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(Mockito.anyString())).thenReturn(prometheusData);
|
||||
dataSourceService.setExecutors(List.of(mockExecutor));
|
||||
|
||||
List<Map<String, Object>> result = dataSourceService.calculate("promql", "node_cpu_seconds_total >= 200");
|
||||
assertEquals(2, result.size());
|
||||
assertNull(result.get(0).get("__value__"));
|
||||
assertEquals(200.0, result.get(1).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void calculate4() {
|
||||
List<Map<String, Object>> prometheusData = 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(Mockito.anyString())).thenReturn(prometheusData);
|
||||
dataSourceService.setExecutors(List.of(mockExecutor));
|
||||
|
||||
List<Map<String, Object>> result = dataSourceService.calculate("promql", "node_cpu_seconds_total > 250");
|
||||
assertEquals(2, result.size());
|
||||
assertNull(result.get(0).get("__value__"));
|
||||
assertNull(result.get(1).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void calculate5() {
|
||||
List<Map<String, Object>> prometheusData = 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(Mockito.anyString())).thenReturn(prometheusData);
|
||||
dataSourceService.setExecutors(List.of(mockExecutor));
|
||||
|
||||
List<Map<String, Object>> result = dataSourceService.calculate("promql", "node_cpu_seconds_total < 100");
|
||||
assertEquals(2, result.size());
|
||||
assertNull(result.get(0).get("__value__"));
|
||||
assertNull(result.get(1).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void calculate6() {
|
||||
List<Map<String, Object>> prometheusData = 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(Mockito.anyString())).thenReturn(prometheusData);
|
||||
dataSourceService.setExecutors(List.of(mockExecutor));
|
||||
|
||||
List<Map<String, Object>> result = dataSourceService.calculate("promql", "node_cpu_seconds_total > 200");
|
||||
assertEquals(2, result.size());
|
||||
assertNull(result.get(0).get("__value__"));
|
||||
assertNull(result.get(1).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void calculate7() {
|
||||
List<Map<String, Object>> prometheusData = 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(Mockito.anyString())).thenReturn(prometheusData);
|
||||
dataSourceService.setExecutors(List.of(mockExecutor));
|
||||
|
||||
List<Map<String, Object>> result = dataSourceService.calculate("promql", "(node_cpu_seconds_total <= 100)");
|
||||
assertEquals(2, result.size());
|
||||
assertEquals(100.0, result.get(0).get("__value__"));
|
||||
assertNull(result.get(1).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void calculate8() {
|
||||
List<Map<String, Object>> prometheusData = 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(Mockito.anyString())).thenReturn(prometheusData);
|
||||
dataSourceService.setExecutors(List.of(mockExecutor));
|
||||
|
||||
List<Map<String, Object>> result = dataSourceService.calculate("promql", "node_cpu_seconds_total{mode=\"user\"}[4m] <= 100");
|
||||
assertEquals(2, result.size());
|
||||
assertEquals(100.0, result.get(0).get("__value__"));
|
||||
assertNull(result.get(1).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void calculate9() {
|
||||
List<Map<String, Object>> prometheusData = 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(Mockito.anyString())).thenReturn(prometheusData);
|
||||
dataSourceService.setExecutors(List.of(mockExecutor));
|
||||
|
||||
List<Map<String, Object>> result = dataSourceService.calculate("promql", "node_cpu_seconds_total{mode=\"user\"} == 100");
|
||||
assertEquals(2, result.size());
|
||||
assertEquals(100.0, result.get(0).get("__value__"));
|
||||
assertNull(result.get(1).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void calculate10() {
|
||||
List<Map<String, Object>> prometheusData = 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(Mockito.anyString())).thenReturn(prometheusData);
|
||||
dataSourceService.setExecutors(List.of(mockExecutor));
|
||||
|
||||
List<Map<String, Object>> result = dataSourceService.calculate("promql", "node_cpu_seconds_total{mode=\"user\"} != 100");
|
||||
assertEquals(2, result.size());
|
||||
assertNull(result.get(0).get("__value__"));
|
||||
assertEquals(200.0, result.get(1).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void calculate11() {
|
||||
List<Map<String, Object>> prometheusData = 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(Mockito.anyString())).thenReturn(prometheusData);
|
||||
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\"} < 120");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(100.0, result.get(0).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void calculate12() {
|
||||
List<Map<String, Object>> prometheusData = 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(Mockito.anyString())).thenReturn(prometheusData);
|
||||
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\"} < 120)");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(100.0, result.get(0).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void calculate13() {
|
||||
List<Map<String, Object>> prometheusData = 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(Mockito.anyString())).thenReturn(prometheusData);
|
||||
dataSourceService.setExecutors(List.of(mockExecutor));
|
||||
|
||||
List<Map<String, Object>> result = dataSourceService.calculate("promql", "node_cpu_seconds_total{mode=\"user\"} > 150 and node_cpu_seconds_total{mode=\"idle\"} < 220");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(200.0, result.get(0).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void calculate14() {
|
||||
List<Map<String, Object>> prometheusData = 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(Mockito.anyString())).thenReturn(prometheusData);
|
||||
dataSourceService.setExecutors(List.of(mockExecutor));
|
||||
|
||||
List<Map<String, Object>> result = dataSourceService.calculate("promql", "(node_cpu_seconds_total{mode=\"user\"} > 150) and (node_cpu_seconds_total{mode=\"idle\"} < 220)");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(200.0, result.get(0).get("__value__"));
|
||||
}
|
||||
|
||||
@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"))
|
||||
);
|
||||
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);
|
||||
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__"));
|
||||
}
|
||||
|
||||
@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"))
|
||||
);
|
||||
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);
|
||||
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");
|
||||
assertEquals(1, result.size());
|
||||
assertNull(result.get(0).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void calculate17() {
|
||||
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"))
|
||||
);
|
||||
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);
|
||||
dataSourceService.setExecutors(List.of(mockExecutor));
|
||||
|
||||
List<Map<String, Object>> result = dataSourceService.calculate("promql", "node_cpu_seconds_total{mode=\"user\"} > 150 or node_cpu_seconds_total{mode=\"idle\"} < 20");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(200.0, result.get(0).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void calculate18() {
|
||||
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"))
|
||||
);
|
||||
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);
|
||||
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\"} < 120");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(100.0, result.get(0).get("__value__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void calculate19() {
|
||||
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"))
|
||||
);
|
||||
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);
|
||||
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__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void calculate20() {
|
||||
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"))
|
||||
);
|
||||
List<Map<String, Object>> prometheusData2 = List.of(
|
||||
new HashMap<>(Map.of("__value__", 100.0, "timestamp", 1343554, "key", "node1")),
|
||||
new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555, "book", "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);
|
||||
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__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void calculate21() {
|
||||
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"))
|
||||
);
|
||||
List<Map<String, Object>> prometheusData2 = List.of(
|
||||
new HashMap<>(Map.of("__value__", 100.0, "timestamp", 1343554, "key", "node1")),
|
||||
new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555, "book", "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);
|
||||
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__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void calculate22() {
|
||||
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"))
|
||||
);
|
||||
List<Map<String, Object>> prometheusData2 = List.of(
|
||||
new HashMap<>(Map.of("__value__", 100.0, "timestamp", 1343554, "key", "node1")),
|
||||
new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555, "book", "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);
|
||||
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__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void calculate23() {
|
||||
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"))
|
||||
);
|
||||
List<Map<String, Object>> prometheusData2 = List.of(
|
||||
new HashMap<>(Map.of("__value__", 100.0, "timestamp", 1343554, "key", "node1")),
|
||||
new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555, "book", "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);
|
||||
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__"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void calculate24() {
|
||||
List<Map<String, Object>> prometheusData = List.of();
|
||||
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(0, result.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void calculate25() {
|
||||
List<Map<String, Object>> prometheusData = List.of();
|
||||
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{mode=\"user\"} > 250 unless node_cpu_seconds_total{mode=\"idle\"} < 20");
|
||||
assertEquals(0, result.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void calculate26() {
|
||||
List<Map<String, Object>> prometheusData = List.of(
|
||||
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(1, result.size());
|
||||
assertEquals(200.0, result.get(0).get("__value__"));
|
||||
}
|
||||
}
|
||||
+24
-137
@@ -17,42 +17,33 @@
|
||||
|
||||
package org.apache.hertzbeat.alert.service;
|
||||
|
||||
import org.apache.hertzbeat.alert.dao.NoticeReceiverDao;
|
||||
import org.apache.hertzbeat.alert.dao.NoticeRuleDao;
|
||||
import org.apache.hertzbeat.alert.dao.NoticeTemplateDao;
|
||||
import org.apache.hertzbeat.alert.notice.AlertNoticeDispatch;
|
||||
import org.apache.hertzbeat.alert.service.impl.NoticeConfigServiceImpl;
|
||||
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeRule;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import org.apache.hertzbeat.alert.service.impl.NoticeConfigServiceImpl;
|
||||
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeRule;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
|
||||
import org.apache.hertzbeat.alert.notice.AlertNoticeDispatch;
|
||||
import org.apache.hertzbeat.alert.dao.NoticeReceiverDao;
|
||||
import org.apache.hertzbeat.alert.dao.NoticeRuleDao;
|
||||
import org.apache.hertzbeat.alert.dao.NoticeTemplateDao;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
|
||||
/**
|
||||
* Test case for {@link NoticeConfigService}
|
||||
*/
|
||||
@Disabled
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class NoticeConfigServiceTest {
|
||||
|
||||
@@ -67,130 +58,26 @@ class NoticeConfigServiceTest {
|
||||
@InjectMocks
|
||||
private NoticeConfigServiceImpl noticeConfigService;
|
||||
|
||||
private NoticeReceiver receiver1;
|
||||
private NoticeReceiver receiver2;
|
||||
private NoticeTemplate template1;
|
||||
private NoticeTemplate template2;
|
||||
private NoticeRule rule1;
|
||||
private NoticeRule rule2;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
receiver1 = new NoticeReceiver();
|
||||
receiver1.setId(1L);
|
||||
receiver1.setName("Receiver1");
|
||||
|
||||
receiver2 = new NoticeReceiver();
|
||||
receiver2.setId(2L);
|
||||
receiver2.setName("Receiver2");
|
||||
|
||||
template1 = new NoticeTemplate();
|
||||
template1.setId(1L);
|
||||
template1.setName("Template1");
|
||||
|
||||
template2 = new NoticeTemplate();
|
||||
template2.setId(2L);
|
||||
template2.setName("Template2");
|
||||
|
||||
rule1 = new NoticeRule();
|
||||
rule1.setId(1L);
|
||||
rule1.setName("Rule1");
|
||||
|
||||
rule2 = new NoticeRule();
|
||||
rule2.setId(2L);
|
||||
rule2.setName("Rule2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getNoticeReceivers() {
|
||||
Page<NoticeReceiver> receiverPage = new PageImpl<>(
|
||||
Arrays.asList(receiver1, receiver2),
|
||||
PageRequest.of(0, 8, Sort.by(Sort.Direction.DESC, "id")),
|
||||
2
|
||||
);
|
||||
|
||||
when(noticeReceiverDao.findAll(any(Specification.class), any(PageRequest.class))).thenReturn(receiverPage);
|
||||
|
||||
Page<NoticeReceiver> result = noticeConfigService.getNoticeReceivers("Receiver", 0, 8);
|
||||
|
||||
assertEquals(2, result.getTotalElements());
|
||||
assertEquals(1, result.getTotalPages());
|
||||
assertEquals(8, result.getSize());
|
||||
assertEquals(0, result.getNumber());
|
||||
assertEquals(receiver1, result.getContent().get(0));
|
||||
assertEquals(receiver2, result.getContent().get(1));
|
||||
|
||||
verify(noticeReceiverDao, times(1)).findAll(any(Specification.class), any(PageRequest.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getAllNoticeReceivers() {
|
||||
when(noticeReceiverDao.findAll()).thenReturn(Arrays.asList(receiver1, receiver2));
|
||||
|
||||
List<NoticeReceiver> result = noticeConfigService.getAllNoticeReceivers();
|
||||
|
||||
assertEquals(2, result.size());
|
||||
assertEquals(receiver1, result.get(0));
|
||||
assertEquals(receiver2, result.get(1));
|
||||
|
||||
verify(noticeReceiverDao, times(1)).findAll();
|
||||
noticeConfigService.getNoticeReceivers(null);
|
||||
verify(noticeReceiverDao, times(1)).findAll(any(Specification.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getNoticeTemplates() {
|
||||
Page<NoticeTemplate> templatePage = new PageImpl<>(
|
||||
Arrays.asList(template1, template2),
|
||||
PageRequest.of(0, 8, Sort.by(Sort.Direction.DESC, "id")),
|
||||
2
|
||||
);
|
||||
|
||||
when(noticeTemplateDao.findAll(any(Specification.class), any(PageRequest.class))).thenReturn(templatePage);
|
||||
|
||||
Page<NoticeTemplate> result = noticeConfigService.getNoticeTemplates("Template", false, 0, 8);
|
||||
|
||||
assertEquals(2, result.getTotalElements());
|
||||
assertEquals(1, result.getTotalPages());
|
||||
assertEquals(8, result.getSize());
|
||||
assertEquals(0, result.getNumber());
|
||||
assertEquals(template1, result.getContent().get(0));
|
||||
assertEquals(template2, result.getContent().get(1));
|
||||
|
||||
verify(noticeTemplateDao, times(1)).findAll(any(Specification.class), any(PageRequest.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getAllNoticeTemplates() {
|
||||
when(noticeTemplateDao.findAll()).thenReturn(Arrays.asList(template1, template2));
|
||||
|
||||
List<NoticeTemplate> result = noticeConfigService.getAllNoticeTemplates();
|
||||
|
||||
assert result.size() >= 2;
|
||||
assertEquals(template1, result.get(result.size() - 2));
|
||||
assertEquals(template2, result.get(result.size() - 1));
|
||||
|
||||
verify(noticeTemplateDao, times(1)).findAll();
|
||||
noticeConfigService.getNoticeTemplates(null);
|
||||
verify(noticeTemplateDao, times(1)).findAll(any(Specification.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getNoticeRules() {
|
||||
Page<NoticeRule> rulePage = new PageImpl<>(
|
||||
Arrays.asList(rule1, rule2),
|
||||
PageRequest.of(0, 8, Sort.by(Sort.Direction.DESC, "id")),
|
||||
2
|
||||
);
|
||||
|
||||
when(noticeRuleDao.findAll(any(Specification.class), any(PageRequest.class))).thenReturn(rulePage);
|
||||
|
||||
Page<NoticeRule> result = noticeConfigService.getNoticeRules("Rule", 0, 8);
|
||||
|
||||
assertEquals(2, result.getTotalElements());
|
||||
assertEquals(1, result.getTotalPages());
|
||||
assertEquals(8, result.getSize());
|
||||
assertEquals(0, result.getNumber());
|
||||
assertEquals(rule1, result.getContent().get(0));
|
||||
assertEquals(rule2, result.getContent().get(1));
|
||||
|
||||
verify(noticeRuleDao, times(1)).findAll(any(Specification.class), any(PageRequest.class));
|
||||
noticeConfigService.getNoticeRules(null);
|
||||
verify(noticeRuleDao, times(1)).findAll(any(Specification.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
-64
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
* 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.config.TwilioSmsProperties;
|
||||
import org.apache.hertzbeat.alert.service.impl.TwilioSmsClientImpl;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.CsvSource;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* Test case for {@link TwilioSmsClientImpl}
|
||||
*/
|
||||
class TwilioSmsClientImplTest {
|
||||
|
||||
@Test
|
||||
void getType() {
|
||||
TwilioSmsProperties twilioSmsProperties = new TwilioSmsProperties();
|
||||
twilioSmsProperties.setAccountSid("accountSid");
|
||||
twilioSmsProperties.setAuthToken("authToken");
|
||||
twilioSmsProperties.setTwilioPhoneNumber("twilioPhoneNumber");
|
||||
TwilioSmsClientImpl twilioSmsClient = new TwilioSmsClientImpl(twilioSmsProperties);
|
||||
|
||||
assertEquals("twilio", twilioSmsClient.getType());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource({
|
||||
"accountSid, authToken, twilioPhoneNumber, true",
|
||||
"accountSid, authToken, '', false",
|
||||
"accountSid, '', twilioPhoneNumber, false",
|
||||
"accountSid, '', '', false",
|
||||
"'', authToken, twilioPhoneNumber, false",
|
||||
"'', authToken, '', false",
|
||||
"'', '', twilioPhoneNumber, false",
|
||||
"'', '', '', false",
|
||||
})
|
||||
void checkConfig(String accountSid, String authToken, String twilioPhoneNumber, boolean expected) {
|
||||
TwilioSmsProperties twilioSmsProperties = new TwilioSmsProperties();
|
||||
twilioSmsProperties.setAccountSid(accountSid);
|
||||
twilioSmsProperties.setAuthToken(authToken);
|
||||
twilioSmsProperties.setTwilioPhoneNumber(twilioPhoneNumber);
|
||||
TwilioSmsClientImpl twilioSmsClient = new TwilioSmsClientImpl(twilioSmsProperties);
|
||||
|
||||
assertEquals(expected, twilioSmsClient.checkConfig());
|
||||
}
|
||||
}
|
||||
@@ -155,14 +155,5 @@
|
||||
<artifactId>plc4j-driver-modbus</artifactId>
|
||||
<version>0.12.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.plc4x</groupId>
|
||||
<artifactId>plc4j-driver-s7</artifactId>
|
||||
<version>0.12.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.sshd</groupId>
|
||||
<artifactId>sshd-sftp</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
+51
-87
@@ -26,25 +26,20 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.collector.collect.AbstractCollect;
|
||||
import org.apache.hertzbeat.collector.collect.common.cache.AbstractConnection;
|
||||
import org.apache.hertzbeat.collector.collect.common.cache.CacheIdentifier;
|
||||
import org.apache.hertzbeat.collector.collect.common.cache.GlobalConnectionCache;
|
||||
import org.apache.hertzbeat.collector.collect.common.cache.JdbcConnect;
|
||||
import org.apache.hertzbeat.collector.collect.common.ssh.SshTunnelHelper;
|
||||
import org.apache.hertzbeat.collector.constants.CollectorConstants;
|
||||
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.entity.job.Metrics;
|
||||
import org.apache.hertzbeat.common.entity.job.SshTunnel;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.JdbcProtocol;
|
||||
import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
import org.apache.hertzbeat.common.util.CommonUtil;
|
||||
import org.apache.sshd.common.SshException;
|
||||
import org.apache.sshd.common.channel.exception.SshChannelOpenException;
|
||||
import org.postgresql.util.PSQLException;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.jdbc.datasource.init.ScriptUtils;
|
||||
@@ -60,7 +55,7 @@ public class JdbcCommonCollect extends AbstractCollect {
|
||||
private static final String QUERY_TYPE_MULTI_ROW = "multiRow";
|
||||
private static final String QUERY_TYPE_COLUMNS = "columns";
|
||||
private static final String RUN_SCRIPT = "runScript";
|
||||
|
||||
|
||||
private static final String[] VULNERABLE_KEYWORDS = {"allowLoadLocalInfile", "allowLoadLocalInfileInPath", "useLocalInfile"};
|
||||
|
||||
private final GlobalConnectionCache connectionCommonCache = GlobalConnectionCache.getInstance();
|
||||
@@ -78,29 +73,18 @@ public class JdbcCommonCollect extends AbstractCollect {
|
||||
}
|
||||
}
|
||||
}
|
||||
SshTunnelHelper.checkTunnelParam(metrics.getJdbc().getSshTunnel());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void collect(CollectRep.MetricsData.Builder builder, Metrics metrics) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
JdbcProtocol jdbcProtocol = metrics.getJdbc();
|
||||
SshTunnel sshTunnel = jdbcProtocol.getSshTunnel();
|
||||
|
||||
String databaseUrl = constructDatabaseUrl(jdbcProtocol);
|
||||
int timeout = CollectUtil.getTimeout(jdbcProtocol.getTimeout());
|
||||
boolean reuseConnection = Boolean.parseBoolean(jdbcProtocol.getReuseConnection());
|
||||
Statement statement = null;
|
||||
String databaseUrl;
|
||||
try {
|
||||
if (sshTunnel != null && Boolean.parseBoolean(sshTunnel.getEnable())) {
|
||||
int localPort = SshTunnelHelper.localPortForward(sshTunnel, jdbcProtocol.getHost(), jdbcProtocol.getPort());
|
||||
databaseUrl = constructDatabaseUrl(jdbcProtocol, "localhost", String.valueOf(localPort));
|
||||
} else {
|
||||
databaseUrl = constructDatabaseUrl(jdbcProtocol, jdbcProtocol.getHost(), jdbcProtocol.getPort());
|
||||
}
|
||||
|
||||
statement = getConnection(jdbcProtocol.getUsername(),
|
||||
jdbcProtocol.getPassword(), databaseUrl, timeout, reuseConnection);
|
||||
jdbcProtocol.getPassword(), databaseUrl, timeout);
|
||||
switch (jdbcProtocol.getQueryType()) {
|
||||
case QUERY_TYPE_ONE_ROW -> queryOneRow(statement, jdbcProtocol.getSql(), metrics.getAliasFields(), builder, startTime);
|
||||
case QUERY_TYPE_MULTI_ROW -> queryMultiRow(statement, jdbcProtocol.getSql(), metrics.getAliasFields(), builder, startTime);
|
||||
@@ -128,14 +112,6 @@ public class JdbcCommonCollect extends AbstractCollect {
|
||||
log.warn("Jdbc sql error: {}, code: {}.", sqlException.getMessage(), sqlException.getErrorCode());
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg("Query Error: " + sqlException.getMessage() + " Code: " + sqlException.getErrorCode());
|
||||
} catch (SshException sshException) {
|
||||
Throwable throwable = sshException.getCause();
|
||||
if (throwable instanceof SshChannelOpenException) {
|
||||
log.warn("[Jdbc collect] Remote ssh server no more session channel, please increase sshd_config MaxSessions.");
|
||||
}
|
||||
String errorMsg = CommonUtil.getMessageFromThrowable(sshException);
|
||||
builder.setCode(CollectRep.Code.UN_CONNECTABLE);
|
||||
builder.setMsg("Peer ssh connection failed: " + errorMsg);
|
||||
} catch (Exception e) {
|
||||
String errorMessage = CommonUtil.getMessageFromThrowable(e);
|
||||
log.error("Jdbc error: {}.", errorMessage, e);
|
||||
@@ -143,20 +119,11 @@ public class JdbcCommonCollect extends AbstractCollect {
|
||||
builder.setMsg("Query Error: " + errorMessage);
|
||||
} finally {
|
||||
if (statement != null) {
|
||||
Connection connection = null;
|
||||
try {
|
||||
connection = statement.getConnection();
|
||||
statement.close();
|
||||
} catch (Exception e) {
|
||||
log.error("Jdbc close statement error: {}", e.getMessage());
|
||||
}
|
||||
try {
|
||||
if (!reuseConnection && connection != null) {
|
||||
connection.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Jdbc close connection error: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -167,40 +134,38 @@ public class JdbcCommonCollect extends AbstractCollect {
|
||||
}
|
||||
|
||||
|
||||
private Statement getConnection(String username, String password, String url, Integer timeout, boolean reuseConnection) throws Exception {
|
||||
private Statement getConnection(String username, String password, String url, Integer timeout) throws Exception {
|
||||
CacheIdentifier identifier = CacheIdentifier.builder()
|
||||
.ip(url)
|
||||
.username(username).password(password).build();
|
||||
Optional<AbstractConnection<?>> cacheOption = connectionCommonCache.getCache(identifier, true);
|
||||
Statement statement = null;
|
||||
if (reuseConnection) {
|
||||
Optional<AbstractConnection<?>> cacheOption = connectionCommonCache.getCache(identifier, true);
|
||||
if (cacheOption.isPresent()) {
|
||||
JdbcConnect jdbcConnect = (JdbcConnect) cacheOption.get();
|
||||
if (cacheOption.isPresent()) {
|
||||
JdbcConnect jdbcConnect = (JdbcConnect) cacheOption.get();
|
||||
try {
|
||||
statement = jdbcConnect.getConnection().createStatement();
|
||||
// set query timeout
|
||||
int timeoutSecond = timeout / 1000;
|
||||
timeoutSecond = timeoutSecond <= 0 ? 1 : timeoutSecond;
|
||||
statement.setQueryTimeout(timeoutSecond);
|
||||
// set query max row number
|
||||
statement.setMaxRows(1000);
|
||||
} catch (Exception e) {
|
||||
log.info("The jdbc connect from cache, create statement error: {}", e.getMessage());
|
||||
try {
|
||||
statement = jdbcConnect.getConnection().createStatement();
|
||||
// set query timeout
|
||||
int timeoutSecond = timeout / 1000;
|
||||
timeoutSecond = timeoutSecond <= 0 ? 1 : timeoutSecond;
|
||||
statement.setQueryTimeout(timeoutSecond);
|
||||
// set query max row number
|
||||
statement.setMaxRows(1000);
|
||||
} catch (Exception e) {
|
||||
log.info("The jdbc connect from cache, create statement error: {}", e.getMessage());
|
||||
try {
|
||||
if (statement != null) {
|
||||
statement.close();
|
||||
}
|
||||
jdbcConnect.close();
|
||||
} catch (Exception e2) {
|
||||
log.error(e2.getMessage());
|
||||
if (statement != null) {
|
||||
statement.close();
|
||||
}
|
||||
statement = null;
|
||||
connectionCommonCache.removeCache(identifier);
|
||||
jdbcConnect.close();
|
||||
} catch (Exception e2) {
|
||||
log.error(e2.getMessage());
|
||||
}
|
||||
statement = null;
|
||||
connectionCommonCache.removeCache(identifier);
|
||||
}
|
||||
if (statement != null) {
|
||||
return statement;
|
||||
}
|
||||
}
|
||||
if (statement != null) {
|
||||
return statement;
|
||||
}
|
||||
// renew connection when failed
|
||||
Connection connection = DriverManager.getConnection(url, username, password);
|
||||
@@ -210,10 +175,8 @@ public class JdbcCommonCollect extends AbstractCollect {
|
||||
timeoutSecond = timeoutSecond <= 0 ? 1 : timeoutSecond;
|
||||
statement.setQueryTimeout(timeoutSecond);
|
||||
statement.setMaxRows(1000);
|
||||
if (reuseConnection) {
|
||||
JdbcConnect jdbcConnect = new JdbcConnect(connection);
|
||||
connectionCommonCache.addCache(identifier, jdbcConnect);
|
||||
}
|
||||
JdbcConnect jdbcConnect = new JdbcConnect(connection);
|
||||
connectionCommonCache.addCache(identifier, jdbcConnect);
|
||||
return statement;
|
||||
}
|
||||
|
||||
@@ -222,14 +185,13 @@ public class JdbcCommonCollect extends AbstractCollect {
|
||||
* eg:
|
||||
* query metrics:one tow three four
|
||||
* query sql:select one, tow, three, four from book limit 1;
|
||||
*
|
||||
* @param statement statement
|
||||
* @param sql sql
|
||||
* @param columns query metrics field list
|
||||
* @param sql sql
|
||||
* @param columns query metrics field list
|
||||
* @throws Exception when error happen
|
||||
*/
|
||||
private void queryOneRow(Statement statement, String sql, List<String> columns,
|
||||
CollectRep.MetricsData.Builder builder, long startTime) throws Exception {
|
||||
CollectRep.MetricsData.Builder builder, long startTime) throws Exception {
|
||||
statement.setMaxRows(1);
|
||||
try (ResultSet resultSet = statement.executeQuery(sql)) {
|
||||
if (resultSet.next()) {
|
||||
@@ -254,15 +216,14 @@ public class JdbcCommonCollect extends AbstractCollect {
|
||||
* eg:
|
||||
* query metrics:one two three four
|
||||
* query sql:select key, value from book; the key is the query metrics fields
|
||||
* select key, value from book;
|
||||
* select key, value from book;
|
||||
* one - value1
|
||||
* two - value2
|
||||
* three - value3
|
||||
* four - value4
|
||||
*
|
||||
* @param statement statement
|
||||
* @param sql sql
|
||||
* @param columns query metrics field list
|
||||
* @param sql sql
|
||||
* @param columns query metrics field list
|
||||
* @throws Exception when error happen
|
||||
*/
|
||||
private void queryOneRowByMatchTwoColumns(Statement statement, String sql, List<String> columns,
|
||||
@@ -295,10 +256,9 @@ public class JdbcCommonCollect extends AbstractCollect {
|
||||
* query metrics:one tow three four
|
||||
* query sql:select one, tow, three, four from book;
|
||||
* and return multi row record mapping with the metrics
|
||||
*
|
||||
* @param statement statement
|
||||
* @param sql sql
|
||||
* @param columns query metrics field list
|
||||
* @param sql sql
|
||||
* @param columns query metrics field list
|
||||
* @throws Exception when error happen
|
||||
*/
|
||||
private void queryMultiRow(Statement statement, String sql, List<String> columns,
|
||||
@@ -323,16 +283,14 @@ public class JdbcCommonCollect extends AbstractCollect {
|
||||
|
||||
/**
|
||||
* construct jdbc url due the jdbc protocol
|
||||
*
|
||||
* @param jdbcProtocol jdbc
|
||||
* @return URL
|
||||
*/
|
||||
private String constructDatabaseUrl(JdbcProtocol jdbcProtocol, String host, String port) {
|
||||
private String constructDatabaseUrl(JdbcProtocol jdbcProtocol) {
|
||||
if (Objects.nonNull(jdbcProtocol.getUrl())
|
||||
&& !Objects.equals("", jdbcProtocol.getUrl())
|
||||
&& jdbcProtocol.getUrl().startsWith("jdbc")) {
|
||||
// convert the URL to lowercase for case-insensitive checking
|
||||
String url = jdbcProtocol.getUrl().toLowerCase();
|
||||
String url = jdbcProtocol.getUrl().toLowerCase(); // convert the URL to lowercase for case-insensitive checking
|
||||
// check whether the parameter is valid
|
||||
if (url.contains("create trigger") || url.contains("create alias") || url.contains("runscript from")
|
||||
|| url.contains("allowloadlocalinfile") || url.contains("allowloadlocalinfileinpath")
|
||||
@@ -344,19 +302,25 @@ public class JdbcCommonCollect extends AbstractCollect {
|
||||
return jdbcProtocol.getUrl();
|
||||
}
|
||||
return switch (jdbcProtocol.getPlatform()) {
|
||||
case "mysql", "mariadb" -> "jdbc:mysql://" + host + ":" + port
|
||||
case "mysql", "mariadb" ->
|
||||
"jdbc:mysql://" + jdbcProtocol.getHost() + ":" + jdbcProtocol.getPort()
|
||||
+ "/" + (jdbcProtocol.getDatabase() == null ? "" : jdbcProtocol.getDatabase())
|
||||
+ "?useUnicode=true&characterEncoding=utf-8&useSSL=false";
|
||||
case "postgresql" -> "jdbc:postgresql://" + host + ":" + port
|
||||
case "postgresql" ->
|
||||
"jdbc:postgresql://" + jdbcProtocol.getHost() + ":" + jdbcProtocol.getPort()
|
||||
+ "/" + (jdbcProtocol.getDatabase() == null ? "" : jdbcProtocol.getDatabase());
|
||||
case "clickhouse" -> "jdbc:clickhouse://" + host + ":" + port
|
||||
case "clickhouse" ->
|
||||
"jdbc:clickhouse://" + jdbcProtocol.getHost() + ":" + jdbcProtocol.getPort()
|
||||
+ "/" + (jdbcProtocol.getDatabase() == null ? "" : jdbcProtocol.getDatabase());
|
||||
case "sqlserver" -> "jdbc:sqlserver://" + host + ":" + port
|
||||
case "sqlserver" ->
|
||||
"jdbc:sqlserver://" + jdbcProtocol.getHost() + ":" + jdbcProtocol.getPort()
|
||||
+ ";" + (jdbcProtocol.getDatabase() == null ? "" : "DatabaseName=" + jdbcProtocol.getDatabase())
|
||||
+ ";trustServerCertificate=true;";
|
||||
case "oracle" -> "jdbc:oracle:thin:@" + host + ":" + port
|
||||
case "oracle" ->
|
||||
"jdbc:oracle:thin:@" + jdbcProtocol.getHost() + ":" + jdbcProtocol.getPort()
|
||||
+ "/" + (jdbcProtocol.getDatabase() == null ? "" : jdbcProtocol.getDatabase());
|
||||
case "dm" -> "jdbc:dm://" + host + ":" + port;
|
||||
case "dm" ->
|
||||
"jdbc:dm://" + jdbcProtocol.getHost() + ":" + jdbcProtocol.getPort();
|
||||
default -> throw new IllegalArgumentException("Not support database platform: " + jdbcProtocol.getPlatform());
|
||||
};
|
||||
}
|
||||
|
||||
+26
-104
@@ -17,11 +17,9 @@
|
||||
|
||||
package org.apache.hertzbeat.collector.collect.ftp;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.net.ftp.FTPClient;
|
||||
import org.apache.hertzbeat.collector.collect.AbstractCollect;
|
||||
@@ -31,10 +29,6 @@ import org.apache.hertzbeat.common.entity.job.Metrics;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.FtpProtocol;
|
||||
import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
import org.apache.hertzbeat.common.util.CommonUtil;
|
||||
import org.apache.sshd.client.SshClient;
|
||||
import org.apache.sshd.client.session.ClientSession;
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
import org.apache.sshd.sftp.client.SftpClientFactory;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -62,14 +56,33 @@ public class FtpCollectImpl extends AbstractCollect {
|
||||
Assert.hasText(ftpProtocol.getTimeout(), "Ftp Protocol timeout is required.");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void collect(CollectRep.MetricsData.Builder builder, Metrics metrics) {
|
||||
boolean ssl = Boolean.parseBoolean(metrics.getFtp().getSsl());
|
||||
if (ssl){
|
||||
handleSftpCollect(builder, metrics);
|
||||
} else {
|
||||
handleFtpCollect(builder, metrics);
|
||||
FTPClient ftpClient = new FTPClient();
|
||||
FtpProtocol ftpProtocol = metrics.getFtp();
|
||||
// Set timeout
|
||||
ftpClient.setControlKeepAliveReplyTimeout(Integer.parseInt(ftpProtocol.getTimeout()));
|
||||
|
||||
// Collect data to load in CollectRep.ValueRow.Builder's object
|
||||
CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
|
||||
Map<String, String> valueMap;
|
||||
try {
|
||||
valueMap = collectValue(ftpClient, ftpProtocol);
|
||||
metrics.getAliasFields().forEach(it -> {
|
||||
if (valueMap.containsKey(it)) {
|
||||
String fieldValue = valueMap.get(it);
|
||||
valueRowBuilder.addColumn(Objects.requireNonNullElse(fieldValue, CommonConstants.NULL_VALUE));
|
||||
} else {
|
||||
valueRowBuilder.addColumn(CommonConstants.NULL_VALUE);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
builder.setCode(CollectRep.Code.UN_CONNECTABLE);
|
||||
builder.setMsg(e.getMessage());
|
||||
return;
|
||||
}
|
||||
builder.addValueRow(valueRowBuilder.build());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -100,23 +113,8 @@ public class FtpCollectImpl extends AbstractCollect {
|
||||
};
|
||||
}
|
||||
|
||||
private Map<String, String> collectValue(SftpClient sftpClient, FtpProtocol ftpProtocol) {
|
||||
boolean isActive;
|
||||
String responseTime;
|
||||
try {
|
||||
long startTime = System.currentTimeMillis();
|
||||
sftpClient.stat(ftpProtocol.getDirection());
|
||||
isActive = true;
|
||||
long endTime = System.currentTimeMillis();
|
||||
responseTime = String.valueOf(endTime - startTime);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException("[SFTPClient] error: {}" + CommonUtil.getMessageFromThrowable(e), e);
|
||||
}
|
||||
return Map.of("isActive", Boolean.toString(isActive), "responseTime", responseTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* ftp login
|
||||
* login
|
||||
*/
|
||||
private void login(FTPClient ftpClient, FtpProtocol ftpProtocol) {
|
||||
try {
|
||||
@@ -149,84 +147,8 @@ public class FtpCollectImpl extends AbstractCollect {
|
||||
}
|
||||
}
|
||||
|
||||
private ClientSession connect(SshClient client, FtpProtocol ftpProtocol) {
|
||||
client.start();
|
||||
try {
|
||||
ClientSession session = client.connect(ftpProtocol.getUsername(), ftpProtocol.getHost(), Integer.parseInt(ftpProtocol.getPort()))
|
||||
.verify(Integer.parseInt(ftpProtocol.getTimeout()))
|
||||
.getSession();
|
||||
session.addPasswordIdentity(ftpProtocol.getPassword());
|
||||
session.auth().verify(Integer.parseInt(ftpProtocol.getTimeout()));
|
||||
return session;
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("[sftp connection] error: {}" + CommonUtil.getMessageFromThrowable(e), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String supportProtocol() {
|
||||
return DispatchConstants.PROTOCOL_FTP;
|
||||
}
|
||||
|
||||
private void handleFtpCollect(CollectRep.MetricsData.Builder builder, Metrics metrics) {
|
||||
FTPClient ftpClient = new FTPClient();
|
||||
FtpProtocol ftpProtocol = metrics.getFtp();
|
||||
// Set timeout
|
||||
ftpClient.setControlKeepAliveReplyTimeout(Integer.parseInt(ftpProtocol.getTimeout()));
|
||||
|
||||
// Collect data to load in CollectRep.ValueRow.Builder's object
|
||||
CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
|
||||
Map<String, String> valueMap;
|
||||
try {
|
||||
valueMap = collectValue(ftpClient, ftpProtocol);
|
||||
metrics.getAliasFields().forEach(it -> {
|
||||
if (valueMap.containsKey(it)) {
|
||||
String fieldValue = valueMap.get(it);
|
||||
valueRowBuilder.addColumn(Objects.requireNonNullElse(fieldValue, CommonConstants.NULL_VALUE));
|
||||
} else {
|
||||
valueRowBuilder.addColumn(CommonConstants.NULL_VALUE);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
builder.setCode(CollectRep.Code.UN_CONNECTABLE);
|
||||
builder.setMsg(e.getMessage());
|
||||
return;
|
||||
}
|
||||
builder.addValueRow(valueRowBuilder.build());
|
||||
}
|
||||
|
||||
private void handleSftpCollect(CollectRep.MetricsData.Builder builder, Metrics metrics) {
|
||||
FtpProtocol ftpProtocol = metrics.getFtp();
|
||||
ClientSession session = null;
|
||||
SftpClient sftpClient = null;
|
||||
SshClient client = null;
|
||||
try {
|
||||
client = SshClient.setUpDefaultClient();
|
||||
session = connect(client, ftpProtocol);
|
||||
sftpClient = SftpClientFactory.instance().createSftpClient(session);
|
||||
Map<String, String> valueMap = collectValue(sftpClient, ftpProtocol);
|
||||
CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
|
||||
metrics.getAliasFields().forEach(it ->
|
||||
valueRowBuilder.addColumn(valueMap.getOrDefault(it, CommonConstants.NULL_VALUE))
|
||||
);
|
||||
builder.addValueRow(valueRowBuilder.build());
|
||||
} catch (Exception e) {
|
||||
builder.setCode(CollectRep.Code.UN_CONNECTABLE);
|
||||
builder.setMsg(e.getMessage());
|
||||
} finally {
|
||||
try {
|
||||
if (sftpClient != null && sftpClient.isOpen()){
|
||||
sftpClient.close();
|
||||
}
|
||||
if (session != null && session.isOpen()){
|
||||
session.close();
|
||||
}
|
||||
if (client != null && client.isOpen()){
|
||||
client.close();
|
||||
}
|
||||
} catch (Exception e){
|
||||
log.error("[SFTPClient] error while closing: {}", CommonUtil.getMessageFromThrowable(e), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+63
-310
@@ -18,14 +18,11 @@
|
||||
package org.apache.hertzbeat.collector.collect.http;
|
||||
|
||||
import static org.apache.hertzbeat.common.constants.SignConstants.RIGHT_DASH;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.io.StringReader;
|
||||
import java.net.ConnectException;
|
||||
@@ -37,22 +34,20 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import javax.net.ssl.SSLException;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.xpath.XPath;
|
||||
import javax.xml.xpath.XPathConstants;
|
||||
import javax.xml.xpath.XPathExpressionException;
|
||||
import javax.xml.xpath.XPathFactory;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.net.util.Base64;
|
||||
import org.apache.hertzbeat.collector.collect.AbstractCollect;
|
||||
import org.apache.hertzbeat.collector.collect.common.http.CommonHttpClient;
|
||||
import org.apache.hertzbeat.collector.collect.http.promethus.AbstractPrometheusParse;
|
||||
import org.apache.hertzbeat.collector.collect.http.promethus.PrometheusParseCreator;
|
||||
import org.apache.hertzbeat.collector.collect.prometheus.parser.MetricFamily;
|
||||
import org.apache.hertzbeat.collector.collect.prometheus.parser.OnlineParser;
|
||||
import org.apache.hertzbeat.collector.collect.http.promethus.exporter.ExporterParser;
|
||||
import org.apache.hertzbeat.collector.collect.http.promethus.exporter.MetricFamily;
|
||||
import org.apache.hertzbeat.collector.constants.CollectorConstants;
|
||||
import org.apache.hertzbeat.collector.dispatch.DispatchConstants;
|
||||
import org.apache.hertzbeat.collector.util.CollectUtil;
|
||||
@@ -64,14 +59,15 @@ 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.util.Base64Util;
|
||||
import org.apache.hertzbeat.common.util.CommonUtil;
|
||||
import org.apache.hertzbeat.common.util.IpDomainUtil;
|
||||
import org.apache.http.Header;
|
||||
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;
|
||||
@@ -81,6 +77,8 @@ 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;
|
||||
@@ -88,30 +86,20 @@ import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.util.UriUtils;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
import java.util.Properties;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.HashSet;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* http https collect
|
||||
*/
|
||||
@Slf4j
|
||||
public class HttpCollectImpl extends AbstractCollect {
|
||||
private final Set<Integer> defaultSuccessStatusCodes = Set.of(
|
||||
HttpStatus.SC_OK,
|
||||
HttpStatus.SC_CREATED,
|
||||
HttpStatus.SC_ACCEPTED,
|
||||
HttpStatus.SC_MULTIPLE_CHOICES,
|
||||
HttpStatus.SC_MOVED_PERMANENTLY,
|
||||
HttpStatus.SC_MOVED_TEMPORARILY);
|
||||
private static final Map<Long, ExporterParser> EXPORTER_PARSER_TABLE = new ConcurrentHashMap<>();
|
||||
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 void preCheck(Metrics metrics) throws IllegalArgumentException {
|
||||
@@ -144,11 +132,10 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
builder.setMsg(NetworkConstants.STATUS_CODE + SignConstants.BLANK + statusCode);
|
||||
return;
|
||||
}
|
||||
/*
|
||||
this could create large objects, potentially impacting JVM memory space significantly.
|
||||
Option 1: Parse using InputStream, but this requires significant code changes;
|
||||
Option 2: Manually trigger garbage collection, similar to how it's done in Dubbo for large inputs.
|
||||
*/
|
||||
// todo This code converts an InputStream directly to a String. For large data in Prometheus exporters,
|
||||
// this could create large objects, potentially impacting JVM memory space significantly.
|
||||
// Option 1: Parse using InputStream, but this requires significant code changes;
|
||||
// Option 2: Manually trigger garbage collection, similar to how it's done in Dubbo for large inputs.
|
||||
String resp = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
|
||||
if (!StringUtils.hasText(resp)) {
|
||||
log.info("http response entity is empty, status: {}.", statusCode);
|
||||
@@ -162,17 +149,15 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
case DispatchConstants.PARSE_PROM_QL ->
|
||||
parseResponseByPromQl(resp, metrics.getAliasFields(), metrics.getHttp(), builder);
|
||||
case DispatchConstants.PARSE_PROMETHEUS ->
|
||||
parseResponseByPrometheusExporter(response.getEntity().getContent(), metrics.getAliasFields(), builder);
|
||||
parseResponseByPrometheusExporter(resp, metrics.getAliasFields(), builder);
|
||||
case DispatchConstants.PARSE_XML_PATH ->
|
||||
parseResponseByXmlPath(resp, metrics, builder, responseTime);
|
||||
parseResponseByXmlPath(resp, metrics.getAliasFields(), metrics.getHttp(), builder);
|
||||
case DispatchConstants.PARSE_WEBSITE ->
|
||||
parseResponseByWebsite(resp, metrics, metrics.getHttp(), builder, responseTime);
|
||||
parseResponseByWebsite(resp, metrics, metrics.getHttp(), builder, responseTime, response);
|
||||
case DispatchConstants.PARSE_SITE_MAP ->
|
||||
parseResponseBySiteMap(resp, metrics.getAliasFields(), builder);
|
||||
case DispatchConstants.PARSE_HEADER ->
|
||||
parseResponseByHeader(builder, metrics.getAliasFields(), response);
|
||||
case DispatchConstants.PARSE_CONFIG ->
|
||||
parseResponseByConfig(resp, metrics.getAliasFields(), metrics.getHttp(), builder, responseTime);
|
||||
default ->
|
||||
parseResponseByDefault(resp, metrics.getAliasFields(), metrics.getHttp(), builder, responseTime);
|
||||
}
|
||||
@@ -237,7 +222,8 @@ 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,
|
||||
CloseableHttpResponse response) {
|
||||
CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
|
||||
int keywordNum = CollectUtil.countMatchKeyword(resp, http.getKeyword());
|
||||
for (String alias : metrics.getAliasFields()) {
|
||||
@@ -304,10 +290,9 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
long startTime = System.currentTimeMillis();
|
||||
try {
|
||||
HttpGet httpGet = new HttpGet(siteUrl);
|
||||
try (CloseableHttpResponse response = CommonHttpClient.getHttpClient().execute(httpGet)) {
|
||||
statusCode = response.getStatusLine().getStatusCode();
|
||||
EntityUtils.consume(response.getEntity());
|
||||
}
|
||||
CloseableHttpResponse response = CommonHttpClient.getHttpClient().execute(httpGet);
|
||||
statusCode = response.getStatusLine().getStatusCode();
|
||||
EntityUtils.consume(response.getEntity());
|
||||
} catch (ClientProtocolException e1) {
|
||||
if (e1.getCause() != null) {
|
||||
errorMsg = e1.getCause().getMessage();
|
||||
@@ -343,207 +328,8 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
}
|
||||
}
|
||||
|
||||
private void parseResponseByXmlPath(String resp, Metrics metrics,
|
||||
CollectRep.MetricsData.Builder builder, Long responseTime) {
|
||||
HttpProtocol http = metrics.getHttp();
|
||||
List<String> aliasFields = metrics.getAliasFields();
|
||||
String xpathExpression = http.getParseScript();
|
||||
if (!StringUtils.hasText(xpathExpression)) {
|
||||
log.warn("Http collect parse type is xmlPath, but the xpath expression is empty.");
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg("XPath expression is empty");
|
||||
return;
|
||||
}
|
||||
int keywordNum = CollectUtil.countMatchKeyword(resp, http.getKeyword());
|
||||
|
||||
try {
|
||||
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
||||
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
|
||||
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
|
||||
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
|
||||
dbf.setXIncludeAware(false);
|
||||
dbf.setExpandEntityReferences(false);
|
||||
|
||||
DocumentBuilder db = dbf.newDocumentBuilder();
|
||||
Document document = db.parse(new InputSource(new StringReader(resp)));
|
||||
|
||||
XPathFactory xpathFactory = XPathFactory.newInstance();
|
||||
XPath xpath = xpathFactory.newXPath();
|
||||
|
||||
NodeList nodeList = (NodeList) xpath.evaluate(xpathExpression, document, XPathConstants.NODESET);
|
||||
|
||||
if (nodeList == null || nodeList.getLength() == 0) {
|
||||
log.debug("XPath expression '{}' returned no nodes.", xpathExpression);
|
||||
boolean requestedSummaryFields = aliasFields.stream()
|
||||
.anyMatch(alias -> NetworkConstants.RESPONSE_TIME.equalsIgnoreCase(alias)
|
||||
|| CollectorConstants.KEYWORD.equalsIgnoreCase(alias));
|
||||
|
||||
if (requestedSummaryFields) {
|
||||
CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
|
||||
for (String alias : aliasFields) {
|
||||
if (NetworkConstants.RESPONSE_TIME.equalsIgnoreCase(alias)) {
|
||||
valueRowBuilder.addColumn(responseTime.toString());
|
||||
} else if (CollectorConstants.KEYWORD.equalsIgnoreCase(alias)) {
|
||||
valueRowBuilder.addColumn(Integer.toString(keywordNum));
|
||||
} else {
|
||||
valueRowBuilder.addColumn(CommonConstants.NULL_VALUE);
|
||||
}
|
||||
}
|
||||
builder.addValueRow(valueRowBuilder.build());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < nodeList.getLength(); i++) {
|
||||
Node node = nodeList.item(i);
|
||||
CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
|
||||
|
||||
for (String alias : aliasFields) {
|
||||
if (NetworkConstants.RESPONSE_TIME.equalsIgnoreCase(alias)) {
|
||||
valueRowBuilder.addColumn(responseTime.toString());
|
||||
} else if (CollectorConstants.KEYWORD.equalsIgnoreCase(alias)) {
|
||||
valueRowBuilder.addColumn(Integer.toString(keywordNum));
|
||||
} else {
|
||||
try {
|
||||
String value = (String) xpath.evaluate(alias, node, XPathConstants.STRING);
|
||||
valueRowBuilder.addColumn(StringUtils.hasText(value) ? value : CommonConstants.NULL_VALUE);
|
||||
} catch (XPathExpressionException e) {
|
||||
log.warn("Failed to evaluate XPath '{}' for node [{}]: {}", alias, node.getNodeName(), e.getMessage());
|
||||
valueRowBuilder.addColumn(CommonConstants.NULL_VALUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
builder.addValueRow(valueRowBuilder.build());
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to parse XML response with XPath '{}': {}", xpathExpression, e.getMessage(), e);
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg("Failed to parse XML response: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Parses the response body in Properties/Config format.
|
||||
* Two modes are supported:
|
||||
* 1. single-object mode: if http.parseScript is null, aliasFields are treated as indicator names.
|
||||
* - If there is a locator in the indicator definition, use the locator as the key of the Properties.
|
||||
* - Otherwise, use aliasField (metric name) as the key for Properties.
|
||||
* Generate a single row of data.
|
||||
* 2. array mode: if http.parseScript is not empty (e.g. “users”), treat it as an array base path.
|
||||
* Treat aliasFields as the attribute name of an array element, and generate a single row of data for each array index. locator is invalid in this mode.
|
||||
*
|
||||
* @param resp Response body string
|
||||
* @param aliasFields List of metrics aliases (i.e., the list of fields in metrics.fields).
|
||||
* @param http http protocol configuration
|
||||
* @param builder The metrics data builder.
|
||||
* @param responseTime response time
|
||||
*/
|
||||
private void parseResponseByConfig(String resp, List<String> aliasFields, HttpProtocol http,
|
||||
CollectRep.MetricsData.Builder builder, Long responseTime) {
|
||||
if (!StringUtils.hasText(resp)) {
|
||||
log.warn("Http collect parse type is config, but response body is empty.");
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg("Response body is empty");
|
||||
return;
|
||||
}
|
||||
|
||||
Properties properties = new Properties();
|
||||
try (StringReader reader = new StringReader(resp)) {
|
||||
properties.load(reader);
|
||||
} catch (IOException e) {
|
||||
log.warn("Failed to parse config response: {}", e.getMessage(), e);
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg("Failed to parse config response: " + e.getMessage());
|
||||
return;
|
||||
}
|
||||
String arrayBasePath = http.getParseScript();
|
||||
int keywordNum = CollectUtil.countMatchKeyword(resp, http.getKeyword());
|
||||
|
||||
if (!StringUtils.hasText(arrayBasePath)) {
|
||||
CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
|
||||
for (String alias : aliasFields) {
|
||||
if (NetworkConstants.RESPONSE_TIME.equalsIgnoreCase(alias)) {
|
||||
valueRowBuilder.addColumn(responseTime.toString());
|
||||
} else if (CollectorConstants.KEYWORD.equalsIgnoreCase(alias)) {
|
||||
valueRowBuilder.addColumn(Integer.toString(keywordNum));
|
||||
} else {
|
||||
String value = properties.getProperty(alias);
|
||||
valueRowBuilder.addColumn(value != null ? value : CommonConstants.NULL_VALUE);
|
||||
}
|
||||
}
|
||||
CollectRep.ValueRow valueRow = valueRowBuilder.build();
|
||||
if (hasMeaningfulDataInRow(valueRow, aliasFields)) {
|
||||
builder.addValueRow(valueRow);
|
||||
} else {
|
||||
log.warn("No meaningful data found in single config object response for aliasFields: {}", aliasFields);
|
||||
}
|
||||
} else {
|
||||
Pattern pattern = Pattern.compile("^" + Pattern.quote(arrayBasePath) + "\\[(\\d+)]\\.");
|
||||
Set<Integer> existingIndices = new HashSet<>();
|
||||
for (String key : properties.stringPropertyNames()) {
|
||||
Matcher matcher = pattern.matcher(key);
|
||||
if (matcher.find()) {
|
||||
try {
|
||||
int index = Integer.parseInt(matcher.group(1));
|
||||
existingIndices.add(index);
|
||||
} catch (NumberFormatException e) {
|
||||
log.error("Could not parse index from key: {}", key);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (existingIndices.isEmpty()) {
|
||||
log.warn("Could not find any array elements for base path '{}' in config response.", arrayBasePath);
|
||||
return;
|
||||
}
|
||||
List<Integer> sortedIndices = new ArrayList<>(existingIndices);
|
||||
Collections.sort(sortedIndices);
|
||||
for (int i : sortedIndices) {
|
||||
CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
|
||||
for (String alias : aliasFields) {
|
||||
if (NetworkConstants.RESPONSE_TIME.equalsIgnoreCase(alias)) {
|
||||
valueRowBuilder.addColumn(responseTime.toString());
|
||||
} else if (CollectorConstants.KEYWORD.equalsIgnoreCase(alias)) {
|
||||
valueRowBuilder.addColumn(Integer.toString(keywordNum));
|
||||
} else {
|
||||
String currentKey = arrayBasePath + "[" + i + "]." + alias;
|
||||
String value = properties.getProperty(currentKey);
|
||||
valueRowBuilder.addColumn(value != null ? value : CommonConstants.NULL_VALUE);
|
||||
}
|
||||
}
|
||||
CollectRep.ValueRow valueRow = valueRowBuilder.build();
|
||||
if (hasMeaningfulDataInRow(valueRow, aliasFields)) {
|
||||
builder.addValueRow(valueRowBuilder.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasMeaningfulDataInRow(CollectRep.ValueRow valueRow, List<String> aliasFields) {
|
||||
if (valueRow.getColumnsCount() == 0) {
|
||||
return false;
|
||||
}
|
||||
if (valueRow.getColumnsCount() != aliasFields.size()) {
|
||||
log.error("Column count ({}) mismatch with aliasFields size ({}) when checking meaningful data.",
|
||||
valueRow.getColumnsCount(), aliasFields.size());
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean hasMeaningfulData = false;
|
||||
for (int i = 0; i < valueRow.getColumnsCount(); i++) {
|
||||
String columnValue = valueRow.getColumns(i);
|
||||
String alias = aliasFields.get(i);
|
||||
if (!CommonConstants.NULL_VALUE.equals(columnValue) && (!NetworkConstants.RESPONSE_TIME.equalsIgnoreCase(alias) && !CollectorConstants.KEYWORD.equalsIgnoreCase(alias))) {
|
||||
hasMeaningfulData = true;
|
||||
break;
|
||||
}
|
||||
if ((NetworkConstants.RESPONSE_TIME.equalsIgnoreCase(alias) || CollectorConstants.KEYWORD.equalsIgnoreCase(alias)) && !CommonConstants.NULL_VALUE.equals(columnValue)) {
|
||||
hasMeaningfulData = true;
|
||||
}
|
||||
}
|
||||
return hasMeaningfulData;
|
||||
private void parseResponseByXmlPath(String resp, List<String> aliasFields, HttpProtocol http,
|
||||
CollectRep.MetricsData.Builder builder) {
|
||||
}
|
||||
|
||||
private void parseResponseByJsonPath(String resp, List<String> aliasFields, HttpProtocol http,
|
||||
@@ -600,22 +386,36 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
prometheusParser.handle(resp, aliasFields, http, builder);
|
||||
}
|
||||
|
||||
private void parseResponseByPrometheusExporter(InputStream content, List<String> aliasFields, CollectRep.MetricsData.Builder builder) throws IOException {
|
||||
Map<String, MetricFamily> metricFamilyMap = OnlineParser.parseMetrics(content);
|
||||
if (metricFamilyMap == null || metricFamilyMap.isEmpty()) {
|
||||
return;
|
||||
private void parseResponseByPrometheusExporter(String resp, List<String> aliasFields,
|
||||
CollectRep.MetricsData.Builder builder) {
|
||||
if (!EXPORTER_PARSER_TABLE.containsKey(builder.getId())) {
|
||||
EXPORTER_PARSER_TABLE.put(builder.getId(), new ExporterParser());
|
||||
}
|
||||
ExporterParser parser = EXPORTER_PARSER_TABLE.get(builder.getId());
|
||||
Map<String, MetricFamily> metricFamilyMap = parser.textToMetric(resp);
|
||||
String metrics = builder.getMetrics();
|
||||
if (metricFamilyMap.containsKey(metrics)) {
|
||||
MetricFamily metricFamily = metricFamilyMap.get(metrics);
|
||||
for (MetricFamily.Metric metric : metricFamily.getMetricList()) {
|
||||
Map<String, String> labelMap = metric.getLabels()
|
||||
Map<String, String> labelMap = metric.getLabelPair()
|
||||
.stream()
|
||||
.collect(Collectors.toMap(MetricFamily.Label::getName, MetricFamily.Label::getValue));
|
||||
CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
|
||||
for (String aliasField : aliasFields) {
|
||||
if ("value".equals(aliasField)) {
|
||||
valueRowBuilder.addColumn(String.valueOf(metric.getValue()));
|
||||
if (metric.getCounter() != null) {
|
||||
valueRowBuilder.addColumn(String.valueOf(metric.getCounter().getValue()));
|
||||
} else if (metric.getGauge() != null) {
|
||||
valueRowBuilder.addColumn(String.valueOf(metric.getGauge().getValue()));
|
||||
} else if (metric.getUntyped() != null) {
|
||||
valueRowBuilder.addColumn(String.valueOf(metric.getUntyped().getValue()));
|
||||
} else if (metric.getInfo() != null) {
|
||||
valueRowBuilder.addColumn(String.valueOf(metric.getInfo().getValue()));
|
||||
} else if (metric.getSummary() != null) {
|
||||
valueRowBuilder.addColumn(String.valueOf(metric.getSummary().getValue()));
|
||||
} else if (metric.getHistogram() != null) {
|
||||
valueRowBuilder.addColumn(String.valueOf(metric.getHistogram().getValue()));
|
||||
}
|
||||
} else {
|
||||
String columnValue = labelMap.get(aliasField);
|
||||
valueRowBuilder.addColumn(columnValue == null ? CommonConstants.NULL_VALUE : columnValue);
|
||||
@@ -672,10 +472,11 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
CredentialsProvider provider = new BasicCredentialsProvider();
|
||||
UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(auth.getDigestAuthUsername(),
|
||||
auth.getDigestAuthPassword());
|
||||
AuthScope authScope = new AuthScope(httpProtocol.getHost(), Integer.parseInt(httpProtocol.getPort()));
|
||||
provider.setCredentials(authScope, credentials);
|
||||
|
||||
provider.setCredentials(AuthScope.ANY, credentials);
|
||||
AuthCache authCache = new BasicAuthCache();
|
||||
authCache.put(new HttpHost(httpProtocol.getHost(), Integer.parseInt(httpProtocol.getPort())), new DigestScheme());
|
||||
clientContext.setCredentialsProvider(provider);
|
||||
clientContext.setAuthCache(authCache);
|
||||
return clientContext;
|
||||
}
|
||||
}
|
||||
@@ -708,37 +509,13 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
}
|
||||
// params
|
||||
Map<String, String> params = httpProtocol.getParams();
|
||||
boolean enableUrlEncoding = Boolean.parseBoolean(httpProtocol.getEnableUrlEncoding());
|
||||
StringBuilder queryParams = new StringBuilder();
|
||||
|
||||
if (params != null && !params.isEmpty()) {
|
||||
for (Map.Entry<String, String> param : params.entrySet()) {
|
||||
String key = param.getKey();
|
||||
String value = param.getValue();
|
||||
|
||||
if (!StringUtils.hasText(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!queryParams.isEmpty()) {
|
||||
queryParams.append("&");
|
||||
}
|
||||
|
||||
if (enableUrlEncoding) {
|
||||
key = UriUtils.encodeQueryParam(key, "UTF-8");
|
||||
}
|
||||
queryParams.append(key);
|
||||
|
||||
if (StringUtils.hasText(value)) {
|
||||
String calculatedValue = TimeExpressionUtil.calculate(value);
|
||||
if (enableUrlEncoding) {
|
||||
calculatedValue = UriUtils.encodeQueryParam(calculatedValue, "UTF-8");
|
||||
}
|
||||
queryParams.append("=").append(calculatedValue);
|
||||
if (StringUtils.hasText(param.getValue())) {
|
||||
requestBuilder.addParameter(param.getKey(), TimeExpressionUtil.calculate(param.getValue()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The default request header can be overridden if customized
|
||||
// keep-alive
|
||||
requestBuilder.addHeader(HttpHeaders.CONNECTION, NetworkConstants.KEEP_ALIVE);
|
||||
@@ -748,7 +525,8 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
if (headers != null && !headers.isEmpty()) {
|
||||
for (Map.Entry<String, String> header : headers.entrySet()) {
|
||||
if (StringUtils.hasText(header.getValue())) {
|
||||
requestBuilder.addHeader(header.getKey(), header.getValue());
|
||||
requestBuilder.addHeader(CollectUtil.replaceUriSpecialChar(header.getKey()),
|
||||
CollectUtil.replaceUriSpecialChar(header.getValue()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -771,7 +549,7 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
if (StringUtils.hasText(authorization.getBasicAuthUsername())
|
||||
&& StringUtils.hasText(authorization.getBasicAuthPassword())) {
|
||||
String authStr = authorization.getBasicAuthUsername() + SignConstants.DOUBLE_MARK + authorization.getBasicAuthPassword();
|
||||
String encodedAuth = Base64Util.encode(authStr);
|
||||
String encodedAuth = new String(Base64.encodeBase64(authStr.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8);
|
||||
requestBuilder.addHeader(HttpHeaders.AUTHORIZATION, DispatchConstants.BASIC + SignConstants.BLANK + encodedAuth);
|
||||
}
|
||||
}
|
||||
@@ -779,32 +557,14 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
|
||||
// if it has payload, would override post params
|
||||
if (StringUtils.hasLength(httpProtocol.getPayload()) && (HttpMethod.POST.matches(httpMethod) || HttpMethod.PUT.matches(httpMethod))) {
|
||||
requestBuilder.setEntity(new StringEntity(TimeExpressionUtil.calculate(httpProtocol.getPayload()), StandardCharsets.UTF_8));
|
||||
requestBuilder.setEntity(new StringEntity(httpProtocol.getPayload(), StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
// uri encode
|
||||
String uri;
|
||||
if (enableUrlEncoding) {
|
||||
// if the url contains parameters directly
|
||||
if (httpProtocol.getUrl().contains("?")) {
|
||||
String path = httpProtocol.getUrl().substring(0, httpProtocol.getUrl().indexOf("?"));
|
||||
String query = httpProtocol.getUrl().substring(httpProtocol.getUrl().indexOf("?") + 1);
|
||||
uri = UriUtils.encodePath(path, "UTF-8") + "?" + UriUtils.encodeQuery(query, "UTF-8");
|
||||
} else {
|
||||
uri = UriUtils.encodePath(httpProtocol.getUrl(), "UTF-8");
|
||||
}
|
||||
} else {
|
||||
uri = httpProtocol.getUrl();
|
||||
}
|
||||
|
||||
// append query params
|
||||
if (!queryParams.isEmpty()) {
|
||||
uri += (uri.contains("?") ? "&" : "?") + queryParams;
|
||||
}
|
||||
|
||||
String finalUri;
|
||||
// uri
|
||||
String uri = CollectUtil.replaceUriSpecialChar(httpProtocol.getUrl());
|
||||
if (IpDomainUtil.isHasSchema(httpProtocol.getHost())) {
|
||||
finalUri = httpProtocol.getHost() + ":" + httpProtocol.getPort() + uri;
|
||||
|
||||
requestBuilder.setUri(httpProtocol.getHost() + ":" + httpProtocol.getPort() + uri);
|
||||
} else {
|
||||
String ipAddressType = IpDomainUtil.checkIpAddressType(httpProtocol.getHost());
|
||||
String baseUri = NetworkConstants.IPV6.equals(ipAddressType)
|
||||
@@ -812,19 +572,12 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
: String.format("%s:%s%s", httpProtocol.getHost(), httpProtocol.getPort(), uri);
|
||||
boolean ssl = Boolean.parseBoolean(httpProtocol.getSsl());
|
||||
if (ssl) {
|
||||
finalUri = NetworkConstants.HTTPS_HEADER + baseUri;
|
||||
requestBuilder.setUri(NetworkConstants.HTTPS_HEADER + baseUri);
|
||||
} else {
|
||||
finalUri = NetworkConstants.HTTP_HEADER + baseUri;
|
||||
requestBuilder.setUri(NetworkConstants.HTTP_HEADER + baseUri);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
requestBuilder.setUri(finalUri);
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.warn("Invalid URI with illegal characters: {}. User has disabled URL encoding, not applying any encoding.", finalUri);
|
||||
throw e;
|
||||
}
|
||||
|
||||
// custom timeout
|
||||
int timeout = CollectUtil.getTimeout(httpProtocol.getTimeout(), 0);
|
||||
if (timeout > 0) {
|
||||
|
||||
+433
@@ -0,0 +1,433 @@
|
||||
/*
|
||||
* 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.http.promethus.exporter;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.collector.collect.http.promethus.ParseException;
|
||||
import org.apache.hertzbeat.common.util.StrBuffer;
|
||||
|
||||
/**
|
||||
* Resolves the data passed by prometheus's exporter interface http:xxx/metrics
|
||||
* Reference: prometheus text_parse.go code, entry: TextToMetricFamilies
|
||||
*/
|
||||
@Slf4j
|
||||
public class ExporterParser {
|
||||
private static final String HELP = "HELP";
|
||||
private static final String TYPE = "TYPE";
|
||||
private static final String EOF = "EOF";
|
||||
private static final String METRIC_NAME_LABEL = ".name";
|
||||
private static final String QUANTILE_LABEL = "quantile";
|
||||
private static final String BUCKET_LABEL = "le";
|
||||
private static final String NAME_LABEL = "__name__";
|
||||
private static final String SUM_SUFFIX = "_sum";
|
||||
private static final String COUNT_SUFFIX = "_count";
|
||||
|
||||
private static final char LEFT_CURLY_BRACKET = '{';
|
||||
private static final char RIGHT_CURLY_BRACKET = '}';
|
||||
private static final char EQUALS = '=';
|
||||
private static final char QUOTES = '"';
|
||||
private static final char ENTER = '\n';
|
||||
private static final char SPACE = ' ';
|
||||
private static final char COMMA = ',';
|
||||
private final Lock lock = new ReentrantLock();
|
||||
private MetricFamily currentMetricFamily;
|
||||
private String currentQuantile;
|
||||
private String currentBucket;
|
||||
|
||||
public Map<String, MetricFamily> textToMetric(String resp) {
|
||||
// key: metric name, value: metric family
|
||||
Map<String, MetricFamily> metricMap = new ConcurrentHashMap<>(10);
|
||||
lock.lock();
|
||||
try {
|
||||
String[] lines = resp.split("\n");
|
||||
for (String line : lines) {
|
||||
this.parseLine(metricMap, new StrBuffer(line));
|
||||
}
|
||||
return metricMap;
|
||||
} catch (Exception e) {
|
||||
log.error("parse prometheus exporter data error, msg: {}", e.getMessage());
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
return metricMap;
|
||||
}
|
||||
|
||||
private void parseLine(Map<String, MetricFamily> metricMap, StrBuffer buffer) {
|
||||
buffer.skipBlankTabs();
|
||||
if (buffer.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
switch (buffer.charAt(0)) {
|
||||
case '#' -> {
|
||||
buffer.read();
|
||||
this.currentMetricFamily = null;
|
||||
this.parseComment(metricMap, buffer);
|
||||
}
|
||||
case ENTER -> {
|
||||
}
|
||||
default -> {
|
||||
this.currentBucket = null;
|
||||
this.currentQuantile = null;
|
||||
this.parseMetric(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void parseComment(Map<String, MetricFamily> metricMap, StrBuffer buffer) {
|
||||
buffer.skipBlankTabs();
|
||||
if (buffer.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
String token = this.readTokenUnitWhitespace(buffer);
|
||||
if (EOF.equals(token)) {
|
||||
return;
|
||||
}
|
||||
if (!HELP.equals(token) && !TYPE.equals(token)) {
|
||||
log.error("parse comment error {}, start without {} or {}", buffer.toStr(), HELP, TYPE);
|
||||
return;
|
||||
}
|
||||
String metricName = this.readTokenAsMetricName(buffer);
|
||||
this.currentMetricFamily = metricMap.computeIfAbsent(metricName, key -> new MetricFamily());
|
||||
this.currentMetricFamily.setName(metricName);
|
||||
switch (token) {
|
||||
case HELP -> this.parseHelp(buffer);
|
||||
case TYPE -> this.parseType(buffer);
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void parseHelp(StrBuffer line) {
|
||||
line.skipBlankTabs();
|
||||
this.currentMetricFamily.setHelp(line.toStr());
|
||||
}
|
||||
|
||||
private void parseType(StrBuffer line) {
|
||||
line.skipBlankTabs();
|
||||
String type = line.toStr().toLowerCase();
|
||||
MetricType metricType = MetricType.getType(type);
|
||||
if (metricType == null) {
|
||||
throw new ParseException("pare type error");
|
||||
}
|
||||
this.currentMetricFamily.setMetricType(metricType);
|
||||
}
|
||||
|
||||
private void parseMetric(StrBuffer buffer) {
|
||||
String metricName = this.readTokenAsMetricName(buffer);
|
||||
MetricFamily.Label label = new MetricFamily.Label();
|
||||
label.setName(METRIC_NAME_LABEL);
|
||||
label.setValue(metricName);
|
||||
|
||||
if (metricName.isEmpty()) {
|
||||
log.error("error parse metric, metric name is null, line: {}", buffer.toStr());
|
||||
return;
|
||||
}
|
||||
|
||||
List<MetricFamily.Metric> metricList = this.currentMetricFamily.getMetricList();
|
||||
if (metricList == null) {
|
||||
metricList = new ArrayList<>();
|
||||
this.currentMetricFamily.setMetricList(metricList);
|
||||
}
|
||||
|
||||
// TODO For the time being, the data is displayed in the form of labels. If there is a better chart display method in the future, we will optimize it.
|
||||
MetricFamily.Metric metric = new MetricFamily.Metric();
|
||||
metricList.add(metric);
|
||||
|
||||
metric.setLabelPair(new ArrayList<>());
|
||||
metric.getLabelPair().add(label);
|
||||
this.readLabels(metric, buffer);
|
||||
}
|
||||
|
||||
private void readLabels(MetricFamily.Metric metric, StrBuffer buffer) {
|
||||
buffer.skipBlankTabs();
|
||||
if (buffer.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (buffer.charAt(0) == LEFT_CURLY_BRACKET) {
|
||||
buffer.read();
|
||||
this.startReadLabelName(metric, buffer);
|
||||
} else {
|
||||
this.readLabelValue(metric, null, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
private void startReadLabelName(MetricFamily.Metric metric, StrBuffer buffer) {
|
||||
buffer.skipBlankTabs();
|
||||
if (buffer.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (buffer.charAt(0) == RIGHT_CURLY_BRACKET) {
|
||||
buffer.read();
|
||||
buffer.skipBlankTabs();
|
||||
if (buffer.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
this.readLabelValue(metric, new MetricFamily.Label(), buffer);
|
||||
return;
|
||||
}
|
||||
String labelName = this.readTokenAsLabelName(buffer);
|
||||
if (labelName.isEmpty() || labelName.equals(NAME_LABEL)) {
|
||||
throw new ParseException("invalid label name" + labelName + ", label name size = 0 or label name equals " + NAME_LABEL);
|
||||
}
|
||||
MetricFamily.Label label = new MetricFamily.Label();
|
||||
label.setName(labelName);
|
||||
if (buffer.read() != EQUALS) {
|
||||
throw new ParseException("parse error, not match the format of labelName=labelValue");
|
||||
}
|
||||
this.startReadLabelValue(metric, label, buffer);
|
||||
}
|
||||
|
||||
private void startReadLabelValue(MetricFamily.Metric metric, MetricFamily.Label label, StrBuffer buffer) {
|
||||
buffer.skipBlankTabs();
|
||||
if (buffer.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
char c = buffer.read();
|
||||
if (c != QUOTES) {
|
||||
throw new ParseException("expected '\"' at start of label value, line: " + buffer.toStr());
|
||||
}
|
||||
String labelValue = this.readTokenAsLabelValue(buffer);
|
||||
label.setValue(labelValue);
|
||||
if (!this.isValidLabelValue(labelValue)) {
|
||||
throw new ParseException("no valid label value: " + labelValue);
|
||||
}
|
||||
if (this.currentMetricFamily.getMetricType().equals(MetricType.SUMMARY) && label.getName().equals(QUANTILE_LABEL)) {
|
||||
this.currentQuantile = labelValue;
|
||||
} else if (this.currentMetricFamily.getMetricType().equals(MetricType.HISTOGRAM) && label.getName().equals(BUCKET_LABEL)) {
|
||||
this.currentBucket = labelValue;
|
||||
}
|
||||
metric.getLabelPair().add(label);
|
||||
|
||||
if (buffer.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
c = buffer.read();
|
||||
switch (c) {
|
||||
case COMMA -> this.startReadLabelName(metric, buffer);
|
||||
case RIGHT_CURLY_BRACKET -> this.readLabelValue(metric, label, buffer);
|
||||
default -> throw new ParseException("expected '}' or ',' at end of label value, line: " + buffer.toStr());
|
||||
}
|
||||
}
|
||||
|
||||
private void readLabelValue(MetricFamily.Metric metric, MetricFamily.Label label, StrBuffer buffer) {
|
||||
buffer.skipBlankTabs();
|
||||
if (buffer.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
switch (this.currentMetricFamily.getMetricType()) {
|
||||
case INFO -> {
|
||||
MetricFamily.Info info = new MetricFamily.Info();
|
||||
info.setValue(buffer.toDouble());
|
||||
metric.setInfo(info);
|
||||
}
|
||||
case COUNTER -> {
|
||||
MetricFamily.Counter counter = new MetricFamily.Counter();
|
||||
counter.setValue(buffer.toDouble());
|
||||
metric.setCounter(counter);
|
||||
}
|
||||
case GAUGE -> {
|
||||
MetricFamily.Gauge gauge = new MetricFamily.Gauge();
|
||||
gauge.setValue(buffer.toDouble());
|
||||
metric.setGauge(gauge);
|
||||
}
|
||||
case UNTYPED -> {
|
||||
MetricFamily.Untyped untyped = new MetricFamily.Untyped();
|
||||
untyped.setValue(buffer.toDouble());
|
||||
metric.setUntyped(untyped);
|
||||
}
|
||||
case SUMMARY -> {
|
||||
// For the time being, the data is displayed in the form of labels. If there is a better chart display method in the future, we will optimize it.
|
||||
MetricFamily.Summary summary = new MetricFamily.Summary();
|
||||
summary.setValue(buffer.toDouble());
|
||||
metric.setSummary(summary);
|
||||
}
|
||||
case HISTOGRAM -> {
|
||||
// For the time being, the data is displayed in the form of labels. If there is a better chart display method in the future, we will optimize it.
|
||||
MetricFamily.Histogram histogram = new MetricFamily.Histogram();
|
||||
histogram.setValue(buffer.toDouble());
|
||||
metric.setHistogram(histogram);
|
||||
}
|
||||
default -> throw new ParseException("no such type in metricFamily");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the token before the first whitespace
|
||||
*
|
||||
* @param buffer A line data object
|
||||
* @return token unit
|
||||
*/
|
||||
private String readTokenUnitWhitespace(StrBuffer buffer) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
while (!buffer.isEmpty()) {
|
||||
char c = buffer.read();
|
||||
if (c == SPACE) {
|
||||
break;
|
||||
}
|
||||
builder.append(c);
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the metric
|
||||
*
|
||||
* @param buffer A line data object
|
||||
* @return token name
|
||||
*/
|
||||
private String readTokenAsMetricName(StrBuffer buffer) {
|
||||
buffer.skipBlankTabs();
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (this.isValidMetricNameStart(buffer.charAt(0))) {
|
||||
while (!buffer.isEmpty()) {
|
||||
char c = buffer.read();
|
||||
if (!this.isValidMetricNameContinuation(c)) {
|
||||
buffer.rollback();
|
||||
break;
|
||||
}
|
||||
builder.append(c);
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
throw new ParseException("parse metric name error");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the label
|
||||
*
|
||||
* @param buffer A line data object
|
||||
* @return label name
|
||||
*/
|
||||
private String readTokenAsLabelName(StrBuffer buffer) {
|
||||
buffer.skipBlankTabs();
|
||||
StringBuilder builder = new StringBuilder();
|
||||
char c = buffer.read();
|
||||
if (this.isValidLabelNameStart(c)) {
|
||||
builder.append(c);
|
||||
while (!buffer.isEmpty()) {
|
||||
c = buffer.read();
|
||||
if (!this.isValidLabelNameContinuation(c)) {
|
||||
buffer.rollback();
|
||||
break;
|
||||
}
|
||||
builder.append(c);
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
throw new ParseException("parse label name error");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the label
|
||||
*
|
||||
* @param buffer A line data object
|
||||
* @return label value
|
||||
*/
|
||||
private String readTokenAsLabelValue(StrBuffer buffer) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
boolean escaped = false;
|
||||
while (!buffer.isEmpty()) {
|
||||
char c = buffer.read();
|
||||
// Handle '\\' escape sequences
|
||||
if (escaped) {
|
||||
switch (c) {
|
||||
case QUOTES, '\\' -> builder.append(c);
|
||||
case 'n' -> builder.append('\n');
|
||||
default -> throw new ParseException("parse label value error");
|
||||
}
|
||||
escaped = false;
|
||||
} else {
|
||||
switch (c) {
|
||||
case QUOTES -> {
|
||||
return builder.toString();
|
||||
}
|
||||
case ENTER -> throw new ParseException("parse label value error, next line");
|
||||
case '\\' -> escaped = true;
|
||||
default -> builder.append(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a character conforms to the first character rule for metric names
|
||||
*
|
||||
* @param c metric character
|
||||
* @return true/false
|
||||
*/
|
||||
private boolean isValidMetricNameStart(char c) {
|
||||
return isValidLabelNameStart(c) || c == ':';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a character conforms to rules for metric name characters other than the first
|
||||
*
|
||||
* @param c metric character
|
||||
* @return true/false
|
||||
*/
|
||||
private boolean isValidMetricNameContinuation(char c) {
|
||||
return isValidLabelNameContinuation(c) || c == ':';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a character conforms to the first character rule for label names
|
||||
*
|
||||
* @param c metric character
|
||||
* @return true/false
|
||||
*/
|
||||
private boolean isValidLabelNameStart(char c) {
|
||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a character conforms to rules for label name characters other than the first
|
||||
*
|
||||
* @param c metric character
|
||||
* @return true/false
|
||||
*/
|
||||
private boolean isValidLabelNameContinuation(char c) {
|
||||
return isValidLabelNameStart(c) || (c >= '0' && c <= '9');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a string is a valid UTF-8 encoded string
|
||||
*
|
||||
* @param s label value
|
||||
* @return true/false
|
||||
*/
|
||||
private boolean isValidLabelValue(String s) {
|
||||
return s != null && s.equals(new String(s.getBytes(StandardCharsets.UTF_8)));
|
||||
}
|
||||
|
||||
private boolean isSum(String s) {
|
||||
return s != null && s.endsWith(SUM_SUFFIX);
|
||||
}
|
||||
|
||||
private boolean isCount(String s) {
|
||||
return s != null && s.endsWith(COUNT_SUFFIX);
|
||||
}
|
||||
|
||||
}
|
||||
+252
@@ -0,0 +1,252 @@
|
||||
/*
|
||||
* 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.http.promethus.exporter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* MetricFamily.
|
||||
*/
|
||||
@Data
|
||||
@ToString
|
||||
public class MetricFamily {
|
||||
/**
|
||||
* metric name
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* metric help
|
||||
*/
|
||||
private String help;
|
||||
|
||||
/**
|
||||
* metric type
|
||||
*/
|
||||
private MetricType metricType;
|
||||
|
||||
/**
|
||||
* Specific metric
|
||||
*/
|
||||
private List<Metric> metricList;
|
||||
|
||||
/**
|
||||
* Metric
|
||||
*/
|
||||
@Data
|
||||
public static class Metric {
|
||||
|
||||
/**
|
||||
* Label data, mainly corresponding to the content within {}
|
||||
*/
|
||||
private List<Label> labelPair;
|
||||
|
||||
/**
|
||||
* info
|
||||
*/
|
||||
private Info info;
|
||||
|
||||
/**
|
||||
* gauge
|
||||
*/
|
||||
private Gauge gauge;
|
||||
|
||||
/**
|
||||
* counter
|
||||
*/
|
||||
private Counter counter;
|
||||
|
||||
/**
|
||||
* summary
|
||||
*/
|
||||
private Summary summary;
|
||||
|
||||
/**
|
||||
* untyped
|
||||
*/
|
||||
private Untyped untyped;
|
||||
|
||||
/**
|
||||
* histogram
|
||||
*/
|
||||
private Histogram histogram;
|
||||
|
||||
/**
|
||||
* timestampMs
|
||||
*/
|
||||
private Long timestampMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Label
|
||||
*/
|
||||
@Data
|
||||
public static class Label {
|
||||
|
||||
/**
|
||||
* name
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* value
|
||||
*/
|
||||
private String value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Info
|
||||
*/
|
||||
@Data
|
||||
public static class Info {
|
||||
|
||||
/**
|
||||
* value
|
||||
*/
|
||||
private double value;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Counter
|
||||
*/
|
||||
@Data
|
||||
public static class Counter {
|
||||
|
||||
/**
|
||||
* value
|
||||
*/
|
||||
private double value;
|
||||
|
||||
// Exemplar
|
||||
}
|
||||
|
||||
/**
|
||||
* Gauge
|
||||
*/
|
||||
@Data
|
||||
public static class Gauge {
|
||||
|
||||
/**
|
||||
* value
|
||||
*/
|
||||
private double value;
|
||||
}
|
||||
|
||||
/**
|
||||
* untyped
|
||||
*/
|
||||
@Data
|
||||
public static class Untyped {
|
||||
|
||||
/**
|
||||
* value
|
||||
*/
|
||||
private double value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Summary
|
||||
*/
|
||||
@Data
|
||||
public static class Summary {
|
||||
|
||||
/**
|
||||
* value
|
||||
*/
|
||||
private double value;
|
||||
|
||||
/**
|
||||
* count
|
||||
*/
|
||||
private long count;
|
||||
|
||||
/**
|
||||
* sum
|
||||
*/
|
||||
private double sum;
|
||||
|
||||
/**
|
||||
* quantileList
|
||||
*/
|
||||
private List<Quantile> quantileList = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Quantile
|
||||
*/
|
||||
@Data
|
||||
public static class Quantile {
|
||||
/**
|
||||
* Corresponding to the quantile field in Prometheus
|
||||
*/
|
||||
private double xLabel;
|
||||
|
||||
/**
|
||||
* value
|
||||
*/
|
||||
private double value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Histogram
|
||||
*/
|
||||
@Data
|
||||
public static class Histogram {
|
||||
|
||||
/**
|
||||
* value
|
||||
*/
|
||||
private double value;
|
||||
|
||||
/**
|
||||
* count
|
||||
*/
|
||||
private long count;
|
||||
|
||||
/**
|
||||
* sum
|
||||
*/
|
||||
private double sum;
|
||||
|
||||
/**
|
||||
* bucketList
|
||||
*/
|
||||
private List<Bucket> bucketList = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Bucket
|
||||
*/
|
||||
@Data
|
||||
public static class Bucket {
|
||||
|
||||
/**
|
||||
* cumulativeCount
|
||||
*/
|
||||
private long cumulativeCount;
|
||||
|
||||
/**
|
||||
* upperBound
|
||||
*/
|
||||
private double upperBound;
|
||||
}
|
||||
}
|
||||
+29
-26
@@ -15,35 +15,38 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.common.constants;
|
||||
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
package org.apache.hertzbeat.collector.collect.http.promethus.exporter;
|
||||
|
||||
/**
|
||||
* Import/Export task status
|
||||
* prometheus metrics type
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
@ToString
|
||||
public enum ImportTaskStatusEnum {
|
||||
|
||||
/**
|
||||
* In progress
|
||||
*/
|
||||
IN_PROGRESS("IN_PROGRESS"),
|
||||
|
||||
/**
|
||||
* Completed
|
||||
*/
|
||||
COMPLETED("COMPLETED"),
|
||||
|
||||
/**
|
||||
* Failed
|
||||
*/
|
||||
FAILED("FAILED");
|
||||
public enum MetricType {
|
||||
// for string metric info
|
||||
INFO("info"),
|
||||
// Represents a monotonically increasing counter, e.g., counting occurrences
|
||||
COUNTER("counter"),
|
||||
// A metric type that can fluctuate up and down, e.g., CPU usage rate
|
||||
GAUGE("gauge"),
|
||||
SUMMARY("summary"),
|
||||
UNTYPED("untyped"),
|
||||
HISTOGRAM("histogram");
|
||||
|
||||
private final String value;
|
||||
|
||||
MetricType(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static MetricType getType(String value) {
|
||||
for (MetricType metricType : values()) {
|
||||
if (metricType.getValue().equals(value)) {
|
||||
return metricType;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+29
-16
@@ -19,7 +19,6 @@ package org.apache.hertzbeat.collector.collect.prometheus;
|
||||
|
||||
import static org.apache.hertzbeat.common.constants.SignConstants.RIGHT_DASH;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.net.ConnectException;
|
||||
import java.net.UnknownHostException;
|
||||
@@ -33,8 +32,10 @@ import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import javax.net.ssl.SSLException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.net.util.Base64;
|
||||
import org.apache.hertzbeat.collector.collect.common.http.CommonHttpClient;
|
||||
import org.apache.hertzbeat.collector.collect.prometheus.parser.MetricFamily;
|
||||
import org.apache.hertzbeat.collector.collect.prometheus.parser.TextParser;
|
||||
import org.apache.hertzbeat.collector.dispatch.DispatchConstants;
|
||||
import org.apache.hertzbeat.collector.util.CollectUtil;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
@@ -43,10 +44,8 @@ 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.hertzbeat.collector.collect.prometheus.parser.OnlineParser;
|
||||
import org.apache.http.HttpHeaders;
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.HttpStatus;
|
||||
@@ -65,6 +64,7 @@ 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;
|
||||
|
||||
@@ -89,8 +89,9 @@ public class PrometheusAutoCollectImpl {
|
||||
}
|
||||
HttpContext httpContext = createHttpContext(metrics.getPrometheus());
|
||||
HttpUriRequest request = createHttpRequest(metrics.getPrometheus());
|
||||
try (CloseableHttpResponse response =
|
||||
CommonHttpClient.getHttpClient().execute(request, httpContext)) {
|
||||
try {
|
||||
CloseableHttpResponse response = CommonHttpClient.getHttpClient()
|
||||
.execute(request, httpContext);
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
boolean isSuccessInvoke = defaultSuccessStatusCodes.contains(statusCode);
|
||||
log.debug("http response status: {}", statusCode);
|
||||
@@ -99,12 +100,25 @@ public class PrometheusAutoCollectImpl {
|
||||
builder.setMsg(NetworkConstants.STATUS_CODE + SignConstants.BLANK + statusCode);
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return parseResponseByPrometheusExporter(response.getEntity().getContent(), builder);
|
||||
} catch (Exception e) {
|
||||
log.info("parse error: {}.", e.getMessage(), e);
|
||||
// todo: The InputStream is directly converted to a String here
|
||||
// For large data in the Prometheus exporter, this can generate large objects, which could severely impact JVM memory space
|
||||
// todo: Option one: Use InputStream for parsing, but this requires significant code changes
|
||||
// Option two: Manually trigger garbage collection, which can be referenced from Dubbo for long i
|
||||
String resp = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
|
||||
long collectTime = System.currentTimeMillis();
|
||||
builder.setTime(collectTime);
|
||||
if (resp == null || !StringUtils.hasText(resp)) {
|
||||
log.error("http response content is empty, status: {}.", statusCode);
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg("parse response data error:" + e.getMessage());
|
||||
builder.setMsg("http response content is empty");
|
||||
} else {
|
||||
try {
|
||||
return parseResponseByPrometheusExporter(resp, metrics.getAliasFields(), builder);
|
||||
} catch (Exception e) {
|
||||
log.info("parse error: {}.", e.getMessage(), e);
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg("parse response data error:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
} catch (ClientProtocolException e1) {
|
||||
String errorMsg = CommonUtil.getMessageFromThrowable(e1);
|
||||
@@ -155,13 +169,12 @@ public class PrometheusAutoCollectImpl {
|
||||
}
|
||||
}
|
||||
|
||||
private List<CollectRep.MetricsData> parseResponseByPrometheusExporter(InputStream inputStream, CollectRep.MetricsData.Builder builder) throws IOException {
|
||||
Map<String, MetricFamily> metricFamilyMap = OnlineParser.parseMetrics(inputStream);
|
||||
private List<CollectRep.MetricsData> parseResponseByPrometheusExporter(String resp, List<String> aliasFields,
|
||||
CollectRep.MetricsData.Builder builder) {
|
||||
Map<String, MetricFamily> metricFamilyMap = TextParser.textToMetricFamilies(resp);
|
||||
List<CollectRep.MetricsData> metricsDataList = new LinkedList<>();
|
||||
if (metricFamilyMap == null) {
|
||||
return metricsDataList;
|
||||
}
|
||||
for (Map.Entry<String, MetricFamily> entry : metricFamilyMap.entrySet()) {
|
||||
builder.clearMetrics();
|
||||
builder.clearFields();
|
||||
builder.clearValues();
|
||||
String metricsName = entry.getKey();
|
||||
@@ -265,7 +278,7 @@ public class PrometheusAutoCollectImpl {
|
||||
if (StringUtils.hasText(authorization.getBasicAuthUsername())
|
||||
&& StringUtils.hasText(authorization.getBasicAuthPassword())) {
|
||||
String authStr = authorization.getBasicAuthUsername() + ":" + authorization.getBasicAuthPassword();
|
||||
String encodedAuth = Base64Util.encode(authStr);
|
||||
String encodedAuth = new String(Base64.encodeBase64(authStr.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8);
|
||||
requestBuilder.addHeader(HttpHeaders.AUTHORIZATION, DispatchConstants.BASIC + " " + encodedAuth);
|
||||
}
|
||||
}
|
||||
|
||||
+70
-88
@@ -17,17 +17,16 @@
|
||||
|
||||
package org.apache.hertzbeat.collector.collect.prometheus.parser;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
|
||||
@@ -37,47 +36,9 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
@Slf4j
|
||||
public class OnlineParser {
|
||||
|
||||
private static final Map<Integer, Integer> escapeMap = new HashMap<>(8);
|
||||
|
||||
static {
|
||||
escapeMap.put((int) 'n', (int) '\n');
|
||||
escapeMap.put((int) 'b', (int) '\b');
|
||||
escapeMap.put((int) 't', (int) '\t');
|
||||
escapeMap.put((int) 'r', (int) '\r');
|
||||
escapeMap.put((int) 'f', (int) '\f');
|
||||
escapeMap.put((int) '\'', (int) '\'');
|
||||
escapeMap.put((int) '\"', (int) '\"');
|
||||
escapeMap.put((int) '\\', (int) '\\');
|
||||
}
|
||||
|
||||
private OnlineParser() {
|
||||
}
|
||||
|
||||
public static Map<String, MetricFamily> parseMetrics(InputStream inputStream) throws IOException {
|
||||
Map<String, MetricFamily> metricFamilyMap = new ConcurrentHashMap<>(10);
|
||||
try {
|
||||
int i = getChar(inputStream);
|
||||
while (i != -1) {
|
||||
if (i == '#' || i == '\n') {
|
||||
skipToLineEnd(inputStream).maybeEol().maybeEof().noElse();
|
||||
} else {
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
stringBuilder.append((char) i);
|
||||
parseMetric(inputStream, metricFamilyMap, stringBuilder);
|
||||
}
|
||||
i = getChar(inputStream);
|
||||
}
|
||||
} catch (FormatException e) {
|
||||
log.error("prometheus parser failed because of wrong input format. {}", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
return metricFamilyMap;
|
||||
}
|
||||
|
||||
private static class FormatException extends Exception {
|
||||
|
||||
public FormatException() {
|
||||
}
|
||||
public FormatException() {}
|
||||
|
||||
public FormatException(String message) {
|
||||
super(message);
|
||||
@@ -156,92 +117,91 @@ public class OnlineParser {
|
||||
return this.i;
|
||||
}
|
||||
|
||||
private int getInt() {
|
||||
private int getInt() throws FormatException {
|
||||
return this.i;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static int getChar(InputStream inputStream) throws IOException, FormatException {
|
||||
private static CharChecker parseOneChar(InputStream inputStream) throws IOException {
|
||||
int i = inputStream.read();
|
||||
if (i == '\\') {
|
||||
i = inputStream.read();
|
||||
if (escapeMap.containsKey(i)) {
|
||||
return escapeMap.get(i);
|
||||
} else {
|
||||
throw new FormatException("Escape character failed.");
|
||||
}
|
||||
} else {
|
||||
return i;
|
||||
}
|
||||
return new CharChecker(i);
|
||||
}
|
||||
|
||||
private static CharChecker parseOneDouble(InputStream inputStream, StringBuilder stringBuilder) throws IOException, FormatException {
|
||||
int i = getChar(inputStream);
|
||||
while (i >= '0' && i <= '9' || i >= 'a' && i <= 'z' || i >= 'A' && i <= 'Z' || i == '-' || i == '+' || i == '.') {
|
||||
private static CharChecker parseOneDouble(InputStream inputStream, StringBuilder stringBuilder) throws IOException {
|
||||
int i = inputStream.read();
|
||||
while ((i >= '0' && i <= '9') || (i >= 'a' && i <= 'z') || (i >= 'A' && i <= 'Z') || i == '-' || i == '+' || i == 'e' || i == '.') {
|
||||
stringBuilder.append((char) i);
|
||||
i = getChar(inputStream);
|
||||
i = inputStream.read();
|
||||
}
|
||||
return new CharChecker(i);
|
||||
}
|
||||
|
||||
private static CharChecker skipOneLong(InputStream inputStream) throws IOException, FormatException {
|
||||
int i = getChar(inputStream);
|
||||
private static CharChecker skipOneLong(InputStream inputStream) throws IOException {
|
||||
int i = inputStream.read();
|
||||
while (i >= '0' && i <= '9') {
|
||||
i = getChar(inputStream);
|
||||
i = inputStream.read();
|
||||
}
|
||||
return new CharChecker(i);
|
||||
}
|
||||
|
||||
private static CharChecker parseMetricName(InputStream inputStream, StringBuilder stringBuilder) throws IOException, FormatException {
|
||||
int i = getChar(inputStream);
|
||||
private static CharChecker parseMetricName(InputStream inputStream, StringBuilder stringBuilder) throws IOException {
|
||||
int i = inputStream.read();
|
||||
while ((i >= 'a' && i <= 'z') || (i >= 'A' && i <= 'Z') || (i >= '0' && i <= '9') || i == '_' || i == ':') {
|
||||
stringBuilder.append((char) i);
|
||||
i = getChar(inputStream);
|
||||
i = inputStream.read();
|
||||
}
|
||||
return new CharChecker(i);
|
||||
}
|
||||
|
||||
private static CharChecker parseLabelName(InputStream inputStream, StringBuilder stringBuilder) throws IOException, FormatException {
|
||||
int i = getChar(inputStream);
|
||||
private static CharChecker parseLabelName(InputStream inputStream, StringBuilder stringBuilder) throws IOException {
|
||||
int i = inputStream.read();
|
||||
while ((i >= 'a' && i <= 'z') || (i >= 'A' && i <= 'Z') || (i >= '0' && i <= '9') || i == '_') {
|
||||
stringBuilder.append((char) i);
|
||||
i = getChar(inputStream);
|
||||
i = inputStream.read();
|
||||
}
|
||||
return new CharChecker(i);
|
||||
}
|
||||
|
||||
private static CharChecker parseLabelValue(InputStream inputStream, StringBuilder stringBuilder) throws IOException, FormatException {
|
||||
int i = getChar(inputStream);
|
||||
int i = inputStream.read();
|
||||
while (i != '"' && i != -1) {
|
||||
if (i == '\\') {
|
||||
i = getChar(inputStream);
|
||||
i = inputStream.read();
|
||||
switch (i) {
|
||||
case 'n' -> stringBuilder.append('\n');
|
||||
case '\\' -> stringBuilder.append('\\');
|
||||
case '\"' -> stringBuilder.append('\"');
|
||||
default -> throw new FormatException();
|
||||
case 'n':
|
||||
stringBuilder.append('\n');
|
||||
break;
|
||||
case '\\':
|
||||
stringBuilder.append('\\');
|
||||
break;
|
||||
case '\"':
|
||||
stringBuilder.append('\"');
|
||||
break;
|
||||
default:
|
||||
throw new FormatException();
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
stringBuilder.append((char) i);
|
||||
}
|
||||
i = getChar(inputStream);
|
||||
i = inputStream.read();
|
||||
}
|
||||
return new CharChecker(i);
|
||||
}
|
||||
|
||||
private static CharChecker skipSpaces(InputStream inputStream) throws IOException, FormatException {
|
||||
int i = getChar(inputStream);
|
||||
private static CharChecker skipSpaces(InputStream inputStream) throws IOException {
|
||||
int i = inputStream.read();
|
||||
while (i == ' ') {
|
||||
i = getChar(inputStream);
|
||||
i = inputStream.read();
|
||||
}
|
||||
return new CharChecker(i);
|
||||
}
|
||||
|
||||
private static CharChecker skipToLineEnd(InputStream inputStream) throws IOException, FormatException {
|
||||
int i = getChar(inputStream);
|
||||
private static CharChecker skipToLineEnd(InputStream inputStream) throws IOException {
|
||||
int i = inputStream.read();
|
||||
while (i != '\n' && i != -1) {
|
||||
i = getChar(inputStream);
|
||||
i = inputStream.read();
|
||||
}
|
||||
return new CharChecker(i);
|
||||
}
|
||||
@@ -296,7 +256,7 @@ public class OnlineParser {
|
||||
}
|
||||
|
||||
private static CharChecker parseMetric(InputStream inputStream, Map<String, MetricFamily> metricFamilyMap, StringBuilder stringBuilder) throws IOException, FormatException {
|
||||
MetricFamily metricFamily;
|
||||
MetricFamily metricFamily = null;
|
||||
MetricFamily.Metric metric = new MetricFamily.Metric();
|
||||
int i = parseMetricName(inputStream, stringBuilder).maybeSpace().maybeLeftBracket().noElse();
|
||||
String metricName = stringBuilder.toString();
|
||||
@@ -307,18 +267,18 @@ public class OnlineParser {
|
||||
metricFamily.setMetricList(new ArrayList<>());
|
||||
metricFamily.setName(metricName);
|
||||
metricFamilyMap.put(metricName, metricFamily);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
metricFamily = metricFamilyMap.get(metricName);
|
||||
}
|
||||
|
||||
if (i == ' ') {
|
||||
i = skipSpaces(inputStream).getInt();
|
||||
}
|
||||
|
||||
List<MetricFamily.Label> labelList = new LinkedList<>();
|
||||
metric.setLabels(labelList);
|
||||
if (i == '{') {
|
||||
List<MetricFamily.Label> labelList = new LinkedList<>();
|
||||
parseLabels(inputStream, stringBuilder, labelList);
|
||||
metric.setLabels(labelList);
|
||||
i = skipSpaces(inputStream).getInt();
|
||||
}
|
||||
|
||||
@@ -344,4 +304,26 @@ public class OnlineParser {
|
||||
metricFamily.getMetricList().add(metric);
|
||||
return new CharChecker(i);
|
||||
}
|
||||
|
||||
public static Map<String, MetricFamily> parseMetrics(InputStream inputStream) throws IOException {
|
||||
Map<String, MetricFamily> metricFamilyMap = new ConcurrentHashMap<>(10);
|
||||
int i = inputStream.read();
|
||||
try {
|
||||
while (i != -1) {
|
||||
if (i == '#' || i == '\n') {
|
||||
skipToLineEnd(inputStream).maybeEol().maybeEof().noElse();
|
||||
} else {
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
stringBuilder.append((char) i);
|
||||
parseMetric(inputStream, metricFamilyMap, stringBuilder);
|
||||
}
|
||||
i = inputStream.read();
|
||||
}
|
||||
} catch (FormatException e) {
|
||||
log.error("prometheus parser failed because of wrong input format. {}", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
return metricFamilyMap;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+3
-3
@@ -17,6 +17,7 @@
|
||||
|
||||
package org.apache.hertzbeat.collector.collect.prometheus.parser;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
@@ -29,10 +30,8 @@ import org.apache.hertzbeat.common.util.StrBuffer;
|
||||
/**
|
||||
* Resolves the data passed by prometheus's exporter interface http:xxx/metrics
|
||||
* Reference: prometheus text_parse.go code, entry: TextToMetricFamilies
|
||||
* recommend use OnlineParser
|
||||
*/
|
||||
@Slf4j
|
||||
@Deprecated(since = "1.7.0")
|
||||
public class TextParser {
|
||||
private static final String NAME_LABEL = "__name__";
|
||||
private static final char LEFT_CURLY_BRACKET = '{';
|
||||
@@ -46,6 +45,7 @@ public class TextParser {
|
||||
|
||||
/**
|
||||
* parser prometheus exporter text metrics data
|
||||
* todo use inputStream bytebuffer instead of resp string
|
||||
* @param resp txt data
|
||||
* @return metrics family
|
||||
*/
|
||||
@@ -300,6 +300,6 @@ public class TextParser {
|
||||
* @return true/false
|
||||
*/
|
||||
private static boolean isValidLabelValue(String s) {
|
||||
return s != null;
|
||||
return s != null && s.equals(new String(s.getBytes(StandardCharsets.UTF_8)));
|
||||
}
|
||||
}
|
||||
|
||||
+8
-7
@@ -58,13 +58,13 @@ public class PushCollectImpl extends AbstractCollect {
|
||||
private static final Map<Long, Long> timeMap = new ConcurrentHashMap<>();
|
||||
|
||||
// ms
|
||||
private static final Integer DEFAULT_TIMEOUT = 3000;
|
||||
private static final Integer timeout = 3000;
|
||||
|
||||
private static final Integer SUCCESS_CODE = 200;
|
||||
|
||||
// It's hard to determine how long ago the first data collection was, because there's no way to know when the last collection occurred.
|
||||
// This makes it difficult to avoid re-collecting data after a restart. The default is 30 seconds
|
||||
private static final Integer FIRST_COLLECT_INTERVAL = 30000;
|
||||
private static final Integer firstCollectInterval = 30000;
|
||||
|
||||
@Override
|
||||
public void preCheck(Metrics metrics) throws IllegalArgumentException {
|
||||
@@ -80,13 +80,14 @@ public class PushCollectImpl extends AbstractCollect {
|
||||
long monitorId = builder.getId();
|
||||
PushProtocol pushProtocol = metrics.getPush();
|
||||
|
||||
Long time = timeMap.getOrDefault(monitorId, curTime - FIRST_COLLECT_INTERVAL);
|
||||
Long time = timeMap.getOrDefault(monitorId, curTime - firstCollectInterval);
|
||||
timeMap.put(monitorId, curTime);
|
||||
|
||||
HttpContext httpContext = createHttpContext(pushProtocol);
|
||||
HttpUriRequest request = createHttpRequest(pushProtocol, monitorId, time);
|
||||
|
||||
try (CloseableHttpResponse response = CommonHttpClient.getHttpClient().execute(request, httpContext)) {
|
||||
try {
|
||||
CloseableHttpResponse response = CommonHttpClient.getHttpClient().execute(request, httpContext);
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
if (statusCode != SUCCESS_CODE) {
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
@@ -145,10 +146,10 @@ public class PushCollectImpl extends AbstractCollect {
|
||||
|
||||
//requestBuilder.setUri(pushProtocol.getUri());
|
||||
|
||||
if (DEFAULT_TIMEOUT > 0) {
|
||||
if (timeout > 0) {
|
||||
RequestConfig requestConfig = RequestConfig.custom()
|
||||
.setConnectTimeout(DEFAULT_TIMEOUT)
|
||||
.setSocketTimeout(DEFAULT_TIMEOUT)
|
||||
.setConnectTimeout(timeout)
|
||||
.setSocketTimeout(timeout)
|
||||
.setRedirectsEnabled(true)
|
||||
.build();
|
||||
requestBuilder.setConfig(requestConfig);
|
||||
|
||||
+18
-56
@@ -28,8 +28,6 @@ import io.lettuce.core.cluster.models.partitions.Partitions;
|
||||
import io.lettuce.core.cluster.models.partitions.RedisClusterNode;
|
||||
import io.lettuce.core.resource.ClientResources;
|
||||
import io.lettuce.core.resource.DefaultClientResources;
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -44,7 +42,6 @@ import org.apache.hertzbeat.collector.collect.common.cache.AbstractConnection;
|
||||
import org.apache.hertzbeat.collector.collect.common.cache.CacheIdentifier;
|
||||
import org.apache.hertzbeat.collector.collect.common.cache.GlobalConnectionCache;
|
||||
import org.apache.hertzbeat.collector.collect.common.cache.RedisConnect;
|
||||
import org.apache.hertzbeat.collector.collect.common.ssh.SshTunnelHelper;
|
||||
import org.apache.hertzbeat.collector.dispatch.DispatchConstants;
|
||||
import org.apache.hertzbeat.collector.util.CollectUtil;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
@@ -54,8 +51,6 @@ import org.apache.hertzbeat.common.entity.job.protocol.RedisProtocol;
|
||||
import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
import org.apache.hertzbeat.common.util.CommonUtil;
|
||||
import org.apache.hertzbeat.common.util.MapCapUtil;
|
||||
import org.apache.sshd.common.SshException;
|
||||
import org.apache.sshd.common.channel.exception.SshChannelOpenException;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -84,7 +79,6 @@ public class RedisCommonCollectImpl extends AbstractCollect {
|
||||
RedisProtocol redisProtocol = metrics.getRedis();
|
||||
Assert.hasText(redisProtocol.getHost(), "Redis Protocol host is required.");
|
||||
Assert.hasText(redisProtocol.getPort(), "Redis Protocol port is required.");
|
||||
SshTunnelHelper.checkTunnelParam(metrics.getRedis().getSshTunnel());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -102,14 +96,6 @@ public class RedisCommonCollectImpl extends AbstractCollect {
|
||||
log.info("[redis connection] error: {}", errorMsg);
|
||||
builder.setCode(CollectRep.Code.UN_CONNECTABLE);
|
||||
builder.setMsg(errorMsg);
|
||||
} catch (SshException sshException) {
|
||||
Throwable throwable = sshException.getCause();
|
||||
if (throwable instanceof SshChannelOpenException) {
|
||||
log.warn("[redis collect] Remote ssh server no more session channel, please increase sshd_config MaxSessions.");
|
||||
}
|
||||
String errorMsg = CommonUtil.getMessageFromThrowable(sshException);
|
||||
builder.setCode(CollectRep.Code.UN_CONNECTABLE);
|
||||
builder.setMsg("Peer ssh connection failed: " + errorMsg);
|
||||
} catch (Exception e) {
|
||||
String errorMsg = CommonUtil.getMessageFromThrowable(e);
|
||||
log.warn("[redis collect] error: {}", e.getMessage(), e);
|
||||
@@ -123,7 +109,7 @@ public class RedisCommonCollectImpl extends AbstractCollect {
|
||||
* @param metrics metrics config
|
||||
* @return data
|
||||
*/
|
||||
private Map<String, String> getSingleRedisInfo(Metrics metrics) throws GeneralSecurityException, IOException {
|
||||
private Map<String, String> getSingleRedisInfo(Metrics metrics) {
|
||||
StatefulRedisConnection<String, String> connection = getSingleConnection(metrics.getRedis());
|
||||
String info = connection.sync().info(metrics.getName());
|
||||
Map<String, String> valueMap = parseInfo(info, metrics);
|
||||
@@ -139,7 +125,7 @@ public class RedisCommonCollectImpl extends AbstractCollect {
|
||||
* @param metrics metrics config
|
||||
* @return data
|
||||
*/
|
||||
private List<Map<String, String>> getClusterRedisInfo(Metrics metrics) throws GeneralSecurityException, IOException {
|
||||
private List<Map<String, String>> getClusterRedisInfo(Metrics metrics) {
|
||||
Map<String, StatefulRedisClusterConnection<String, String>> connectionMap = getConnectionList(metrics.getRedis());
|
||||
List<Map<String, String>> list = new ArrayList<>(connectionMap.size());
|
||||
connectionMap.forEach((identity, connection) ->{
|
||||
@@ -193,16 +179,12 @@ public class RedisCommonCollectImpl extends AbstractCollect {
|
||||
* @param redisProtocol protocol
|
||||
* @return connection
|
||||
*/
|
||||
private StatefulRedisConnection<String, String> getSingleConnection(RedisProtocol redisProtocol) throws GeneralSecurityException, IOException {
|
||||
String[] resolvedArr = resolveHostAndPort(redisProtocol);
|
||||
String host = resolvedArr[0];
|
||||
String port = resolvedArr[1];
|
||||
|
||||
CacheIdentifier identifier = doIdentifier(redisProtocol, host, port);
|
||||
private StatefulRedisConnection<String, String> getSingleConnection(RedisProtocol redisProtocol) {
|
||||
CacheIdentifier identifier = doIdentifier(redisProtocol);
|
||||
StatefulRedisConnection<String, String> connection = (StatefulRedisConnection<String, String>) getStatefulConnection(identifier);
|
||||
if (Objects.isNull(connection)) {
|
||||
// reuse connection failed, new one
|
||||
RedisClient redisClient = buildSingleClient(redisProtocol, host, port);
|
||||
RedisClient redisClient = buildSingleClient(redisProtocol);
|
||||
connection = redisClient.connect();
|
||||
connectionCache.addCache(identifier, new RedisConnect(connection));
|
||||
}
|
||||
@@ -214,7 +196,7 @@ public class RedisCommonCollectImpl extends AbstractCollect {
|
||||
* @param redisProtocol protocol
|
||||
* @return connection map
|
||||
*/
|
||||
private Map<String, StatefulRedisClusterConnection<String, String>> getConnectionList(RedisProtocol redisProtocol) throws GeneralSecurityException, IOException {
|
||||
private Map<String, StatefulRedisClusterConnection<String, String>> getConnectionList(RedisProtocol redisProtocol) {
|
||||
// first connection
|
||||
StatefulRedisClusterConnection<String, String> connection = getClusterConnection(redisProtocol);
|
||||
Partitions partitions = connection.getPartitions();
|
||||
@@ -235,16 +217,12 @@ public class RedisCommonCollectImpl extends AbstractCollect {
|
||||
* @param redisProtocol redis protocol
|
||||
* @return cluster connection
|
||||
*/
|
||||
private StatefulRedisClusterConnection<String, String> getClusterConnection(RedisProtocol redisProtocol) throws GeneralSecurityException, IOException {
|
||||
String[] resolvedArr = resolveHostAndPort(redisProtocol);
|
||||
String host = resolvedArr[0];
|
||||
String port = resolvedArr[1];
|
||||
|
||||
CacheIdentifier identifier = doIdentifier(redisProtocol, host, port);
|
||||
private StatefulRedisClusterConnection<String, String> getClusterConnection(RedisProtocol redisProtocol) {
|
||||
CacheIdentifier identifier = doIdentifier(redisProtocol);
|
||||
StatefulRedisClusterConnection<String, String> connection = (StatefulRedisClusterConnection<String, String>) getStatefulConnection(identifier);
|
||||
if (connection == null) {
|
||||
// reuse connection failed, new one
|
||||
RedisClusterClient redisClusterClient = buildClusterClient(redisProtocol, host, port);
|
||||
RedisClusterClient redisClusterClient = buildClusterClient(redisProtocol);
|
||||
connection = redisClusterClient.connect();
|
||||
connectionCache.addCache(identifier, new RedisConnect(connection));
|
||||
}
|
||||
@@ -282,8 +260,8 @@ public class RedisCommonCollectImpl extends AbstractCollect {
|
||||
* @param redisProtocol redis protocol config
|
||||
* @return redis cluster client
|
||||
*/
|
||||
private RedisClusterClient buildClusterClient(RedisProtocol redisProtocol, String host, String port) {
|
||||
return RedisClusterClient.create(defaultClientResources, redisUri(redisProtocol, host, port));
|
||||
private RedisClusterClient buildClusterClient(RedisProtocol redisProtocol) {
|
||||
return RedisClusterClient.create(defaultClientResources, redisUri(redisProtocol));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -292,12 +270,12 @@ public class RedisCommonCollectImpl extends AbstractCollect {
|
||||
* @param redisProtocol redis protocol config
|
||||
* @return redis single client
|
||||
*/
|
||||
private RedisClient buildSingleClient(RedisProtocol redisProtocol, String host, String port) {
|
||||
return RedisClient.create(defaultClientResources, redisUri(redisProtocol, host, port));
|
||||
private RedisClient buildSingleClient(RedisProtocol redisProtocol) {
|
||||
return RedisClient.create(defaultClientResources, redisUri(redisProtocol));
|
||||
}
|
||||
|
||||
private RedisURI redisUri(RedisProtocol redisProtocol, String host, String port) {
|
||||
RedisURI.Builder redisUriBuilder = RedisURI.builder().withHost(host).withPort(Integer.parseInt(port));
|
||||
private RedisURI redisUri(RedisProtocol redisProtocol) {
|
||||
RedisURI.Builder redisUriBuilder = RedisURI.builder().withHost(redisProtocol.getHost()).withPort(Integer.parseInt(redisProtocol.getPort()));
|
||||
if (StringUtils.hasText(redisProtocol.getUsername())) {
|
||||
redisUriBuilder.withClientName(redisProtocol.getUsername());
|
||||
}
|
||||
@@ -317,10 +295,10 @@ public class RedisCommonCollectImpl extends AbstractCollect {
|
||||
return ip + SignConstants.DOUBLE_MARK + port;
|
||||
}
|
||||
|
||||
private CacheIdentifier doIdentifier(RedisProtocol redisProtocol, String host, String port) {
|
||||
private CacheIdentifier doIdentifier(RedisProtocol redisProtocol) {
|
||||
return CacheIdentifier.builder()
|
||||
.ip(host)
|
||||
.port(port)
|
||||
.ip(redisProtocol.getHost())
|
||||
.port(redisProtocol.getPort())
|
||||
.username(redisProtocol.getUsername())
|
||||
.password(redisProtocol.getPassword())
|
||||
.customArg(redisProtocol.getPattern())
|
||||
@@ -350,22 +328,6 @@ public class RedisCommonCollectImpl extends AbstractCollect {
|
||||
return result;
|
||||
}
|
||||
|
||||
private String[] resolveHostAndPort(RedisProtocol redisProtocol) throws GeneralSecurityException, IOException {
|
||||
boolean enableSshTunnel = Optional.ofNullable(redisProtocol.getSshTunnel())
|
||||
.map(ssh -> Boolean.parseBoolean(ssh.getEnable()))
|
||||
.orElse(false);
|
||||
String host;
|
||||
String port;
|
||||
if (enableSshTunnel){
|
||||
host = "localhost";
|
||||
port = String.valueOf(SshTunnelHelper.localPortForward(redisProtocol.getSshTunnel(), redisProtocol.getHost(), redisProtocol.getPort()));
|
||||
} else {
|
||||
host = redisProtocol.getHost();
|
||||
port = redisProtocol.getPort();
|
||||
}
|
||||
return new String[]{host, port};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String supportProtocol() {
|
||||
return DispatchConstants.PROTOCOL_REDIS;
|
||||
|
||||
-95
@@ -1,95 +0,0 @@
|
||||
/*
|
||||
* 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.s7;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.collector.collect.plc.AbstractPlcCollectImpl;
|
||||
import org.apache.hertzbeat.collector.dispatch.DispatchConstants;
|
||||
import org.apache.hertzbeat.common.entity.job.Metrics;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.PlcProtocol;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.S7Protocol;
|
||||
import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
import org.apache.plc4x.java.api.PlcConnection;
|
||||
import org.apache.plc4x.java.api.messages.PlcReadRequest;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* plc collect
|
||||
*/
|
||||
@Slf4j
|
||||
public class S7CollectImpl extends AbstractPlcCollectImpl {
|
||||
|
||||
@Override
|
||||
public void preCheck(Metrics metrics) throws IllegalArgumentException {
|
||||
S7Protocol s7 = metrics.getS7();
|
||||
List<String> registerAddressList = s7.getRegisterAddresses();
|
||||
// check RackId
|
||||
if (!StringUtils.hasText(s7.getRackId())) {
|
||||
s7.setRackId("0");
|
||||
}
|
||||
// check SlotId
|
||||
if (!StringUtils.hasText(s7.getSlotId())) {
|
||||
s7.setSlotId("0");
|
||||
}
|
||||
// check controllerType
|
||||
if (!StringUtils.hasText(s7.getControllerType())) {
|
||||
s7.setControllerType("S7_1500");
|
||||
}
|
||||
if (!StringUtils.hasText(s7.getTimeout())) {
|
||||
s7.setTimeout("5000");
|
||||
}
|
||||
PlcProtocol plc = metrics.getPlc() == null ? new PlcProtocol() : metrics.getPlc();
|
||||
plc.setRegisterAddresses(registerAddressList);
|
||||
BeanUtils.copyProperties(s7, plc);
|
||||
metrics.setPlc(plc);
|
||||
super.preCheck(metrics);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void collect(CollectRep.MetricsData.Builder builder, Metrics metrics) {
|
||||
super.collect(builder, metrics);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String supportProtocol() {
|
||||
return DispatchConstants.PROTOCOL_S7;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getConnectionString(Metrics metrics) {
|
||||
S7Protocol s7Protocol = metrics.getS7();
|
||||
return "s7://" + s7Protocol.getHost() + ":" + s7Protocol.getPort() + "?remote-rack:" + s7Protocol.getRackId()
|
||||
+ "&remote-slot:" + s7Protocol.getSlotId() + "&controller-type=" + s7Protocol.getControllerType();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PlcReadRequest buildRequest(Metrics metrics, PlcConnection connection) {
|
||||
S7Protocol s7Protocol = metrics.getS7();
|
||||
List<String> registerAddressList = s7Protocol.getRegisterAddresses();
|
||||
// Create a new read request:
|
||||
PlcReadRequest.Builder requestBuilder = connection.readRequestBuilder();
|
||||
for (int i = 0; i < registerAddressList.size(); i++) {
|
||||
String s1 = registerAddressList.get(i);
|
||||
requestBuilder.addTagAddress(metrics.getS7().getAddressSyntax() + ":" + i, s1);
|
||||
}
|
||||
return requestBuilder.build();
|
||||
}
|
||||
}
|
||||
-67
@@ -84,8 +84,6 @@ public class SnmpCollectImpl extends AbstractCollect {
|
||||
+ "{1,choice,0#|1#1 hour, |1<{1,number,integer} hours, }"
|
||||
+ "{2,choice,0#|1#1 minute, |1<{2,number,integer} minutes, }"
|
||||
+ "{3,choice,0#|1#1 second, |1<{3,number,integer} seconds }";
|
||||
private static final String DATE_AND_TIME_PATTERN = "%04d-%02d-%02d,%02d:%02d:%02d.%d";
|
||||
private static final String TIME_ZONE_PATTERN = "%c%02d:%02d";
|
||||
|
||||
private final Map<Integer, Snmp> versionSnmpService = new ConcurrentHashMap<>(3);
|
||||
|
||||
@@ -307,9 +305,6 @@ public class SnmpCollectImpl extends AbstractCollect {
|
||||
CharBuffer res = decoder.decode(ByteBuffer.wrap(bytes));
|
||||
return res.toString();
|
||||
} catch (Exception e) {
|
||||
if (isDateAndTimeOctetString(binding)) {
|
||||
return parseDateAndTime(clearHexStr);
|
||||
}
|
||||
return new String(bytes);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
@@ -320,68 +315,6 @@ public class SnmpCollectImpl extends AbstractCollect {
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isDateAndTimeOctetString(VariableBinding binding) {
|
||||
if (!(binding.getVariable() instanceof OctetString)) {
|
||||
return false;
|
||||
}
|
||||
byte[] bytes = HexFormat.of().parseHex(binding.toValueString().replaceAll(HEX_SPLIT, ""));
|
||||
if (bytes.length != 8 && bytes.length != 11) return false;
|
||||
|
||||
int year = ((bytes[0] & 0xFF) << 8) | (bytes[1] & 0xFF);
|
||||
if (year < 1970 || year > 3000) return false;
|
||||
|
||||
int month = bytes[2] & 0xFF;
|
||||
if (month < 1 || month > 12) return false;
|
||||
|
||||
int day = bytes[3] & 0xFF;
|
||||
if (day < 1 || day > 31) return false;
|
||||
|
||||
int hour = bytes[4] & 0xFF;
|
||||
if (hour > 23) return false;
|
||||
|
||||
int minute = bytes[5] & 0xFF;
|
||||
if (minute > 59) return false;
|
||||
|
||||
int second = bytes[6] & 0xFF;
|
||||
if (second > 59) return false;
|
||||
|
||||
int deciSecond = bytes[7] & 0xFF;
|
||||
if (deciSecond > 99) return false;
|
||||
|
||||
if (bytes.length == 11) {
|
||||
int tzSign = bytes[8] & 0xFF;
|
||||
if (tzSign != 0x2B && tzSign != 0x2D) return false;
|
||||
|
||||
int tzHour = bytes[9] & 0xFF;
|
||||
if (tzHour > 23) return false;
|
||||
|
||||
int tzMinute = bytes[10] & 0xFF;
|
||||
if (tzMinute > 59) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static String parseDateAndTime(String hexString) {
|
||||
byte[] bytes = HexFormat.of().parseHex(hexString);
|
||||
int year = ((bytes[0] & 0xFF) << 8) | (bytes[1] & 0xFF);
|
||||
int month = bytes[2] & 0xFF;
|
||||
int day = bytes[3] & 0xFF;
|
||||
int hour = bytes[4] & 0xFF;
|
||||
int minute = bytes[5] & 0xFF;
|
||||
int second = bytes[6] & 0xFF;
|
||||
int deciSeconds = bytes[7] & 0xFF;
|
||||
String dateTime = String.format(DATE_AND_TIME_PATTERN,
|
||||
year, month, day, hour, minute, second, deciSeconds);
|
||||
if (bytes.length == 11) {
|
||||
char sign = (char) bytes[8];
|
||||
int tzHour = bytes[9] & 0xFF;
|
||||
int tzMinute = bytes[10] & 0xFF;
|
||||
dateTime += String.format(" " + TIME_ZONE_PATTERN, sign, tzHour, tzMinute);
|
||||
}
|
||||
return dateTime;
|
||||
}
|
||||
|
||||
|
||||
private void snmpClose(Snmp snmp, int version) throws IOException {
|
||||
snmp.close();
|
||||
versionSnmpService.remove(version);
|
||||
|
||||
+65
-6
@@ -18,6 +18,7 @@
|
||||
package org.apache.hertzbeat.collector.collect.ssh;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.ConnectException;
|
||||
import java.net.SocketTimeoutException;
|
||||
@@ -29,27 +30,35 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.collector.collect.AbstractCollect;
|
||||
import org.apache.hertzbeat.collector.collect.common.cache.AbstractConnection;
|
||||
import org.apache.hertzbeat.collector.collect.common.cache.CacheIdentifier;
|
||||
import org.apache.hertzbeat.collector.collect.common.cache.GlobalConnectionCache;
|
||||
import org.apache.hertzbeat.collector.collect.common.cache.SshConnect;
|
||||
import org.apache.hertzbeat.collector.collect.common.ssh.CommonSshBlacklist;
|
||||
import org.apache.hertzbeat.collector.collect.common.ssh.SshHelper;
|
||||
import org.apache.hertzbeat.collector.collect.common.ssh.CommonSshClient;
|
||||
import org.apache.hertzbeat.collector.constants.CollectorConstants;
|
||||
import org.apache.hertzbeat.collector.dispatch.DispatchConstants;
|
||||
import org.apache.hertzbeat.collector.util.CollectUtil;
|
||||
import org.apache.hertzbeat.collector.util.PrivateKeyUtils;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.job.Metrics;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.SshProtocol;
|
||||
import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
import org.apache.hertzbeat.common.util.CommonUtil;
|
||||
import org.apache.sshd.client.SshClient;
|
||||
import org.apache.sshd.client.channel.ClientChannel;
|
||||
import org.apache.sshd.client.channel.ClientChannelEvent;
|
||||
import org.apache.sshd.client.session.ClientSession;
|
||||
import org.apache.sshd.common.SshException;
|
||||
import org.apache.sshd.common.channel.exception.SshChannelOpenException;
|
||||
import org.apache.sshd.common.config.keys.FilePasswordProvider;
|
||||
import org.apache.sshd.common.util.io.output.NoCloseOutputStream;
|
||||
import org.apache.sshd.common.util.security.SecurityUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -79,12 +88,11 @@ public class SshCollectImpl extends AbstractCollect {
|
||||
long startTime = System.currentTimeMillis();
|
||||
SshProtocol sshProtocol = metrics.getSsh();
|
||||
boolean reuseConnection = Boolean.parseBoolean(sshProtocol.getReuseConnection());
|
||||
boolean useProxy = Boolean.parseBoolean(sshProtocol.getUseProxy());
|
||||
int timeout = CollectUtil.getTimeout(sshProtocol.getTimeout(), DEFAULT_TIMEOUT);
|
||||
ClientChannel channel = null;
|
||||
ClientSession clientSession = null;
|
||||
try {
|
||||
clientSession = getConnectSession(sshProtocol, timeout, reuseConnection, useProxy);
|
||||
clientSession = getConnectSession(sshProtocol, timeout, reuseConnection);
|
||||
if (CommonSshBlacklist.isCommandBlacklisted(sshProtocol.getScript())) {
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg("The command is blacklisted: " + sshProtocol.getScript());
|
||||
@@ -157,7 +165,7 @@ public class SshCollectImpl extends AbstractCollect {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
if (clientSession != null && !reuseConnection && !useProxy) {
|
||||
if (clientSession != null && !reuseConnection) {
|
||||
try {
|
||||
clientSession.close();
|
||||
} catch (Exception e) {
|
||||
@@ -281,8 +289,59 @@ public class SshCollectImpl extends AbstractCollect {
|
||||
connectionCommonCache.removeCache(identifier);
|
||||
}
|
||||
|
||||
private ClientSession getConnectSession(SshProtocol sshProtocol, int timeout, boolean reuseConnection, boolean useProxy)
|
||||
private ClientSession getConnectSession(SshProtocol sshProtocol, int timeout, boolean reuseConnection)
|
||||
throws IOException, GeneralSecurityException {
|
||||
return SshHelper.getConnectSession(sshProtocol, timeout, reuseConnection, useProxy);
|
||||
CacheIdentifier identifier = CacheIdentifier.builder()
|
||||
.ip(sshProtocol.getHost()).port(sshProtocol.getPort())
|
||||
.username(sshProtocol.getUsername()).password(sshProtocol.getPassword())
|
||||
.build();
|
||||
ClientSession clientSession = null;
|
||||
if (reuseConnection) {
|
||||
Optional<AbstractConnection<?>> cacheOption = connectionCommonCache.getCache(identifier, true);
|
||||
if (cacheOption.isPresent()) {
|
||||
SshConnect sshConnect = (SshConnect) cacheOption.get();
|
||||
clientSession = sshConnect.getConnection();
|
||||
try {
|
||||
if (clientSession == null || clientSession.isClosed() || clientSession.isClosing()) {
|
||||
clientSession = null;
|
||||
connectionCommonCache.removeCache(identifier);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn(e.getMessage());
|
||||
clientSession = null;
|
||||
connectionCommonCache.removeCache(identifier);
|
||||
}
|
||||
}
|
||||
if (clientSession != null) {
|
||||
return clientSession;
|
||||
}
|
||||
}
|
||||
SshClient sshClient = CommonSshClient.getSshClient();
|
||||
clientSession = sshClient.connect(sshProtocol.getUsername(), sshProtocol.getHost(), Integer.parseInt(sshProtocol.getPort()))
|
||||
.verify(timeout, TimeUnit.MILLISECONDS).getSession();
|
||||
if (StringUtils.hasText(sshProtocol.getPassword())) {
|
||||
clientSession.addPasswordIdentity(sshProtocol.getPassword());
|
||||
} else if (StringUtils.hasText(sshProtocol.getPrivateKey())) {
|
||||
var resourceKey = PrivateKeyUtils.writePrivateKey(sshProtocol.getHost(), sshProtocol.getPrivateKey());
|
||||
FilePasswordProvider passwordProvider = (session, resource, index) -> {
|
||||
if (StringUtils.hasText(sshProtocol.getPrivateKeyPassphrase())) {
|
||||
return sshProtocol.getPrivateKeyPassphrase();
|
||||
}
|
||||
return null;
|
||||
};
|
||||
SecurityUtils.loadKeyPairIdentities(null, () -> resourceKey, new FileInputStream(resourceKey), passwordProvider)
|
||||
.forEach(clientSession::addPublicKeyIdentity);
|
||||
} // else auth with localhost private public key certificates
|
||||
|
||||
// auth
|
||||
if (!clientSession.auth().verify(timeout, TimeUnit.MILLISECONDS).isSuccess()) {
|
||||
clientSession.close();
|
||||
throw new IllegalArgumentException("ssh auth failed.");
|
||||
}
|
||||
if (reuseConnection) {
|
||||
SshConnect sshConnect = new SshConnect(clientSession);
|
||||
connectionCommonCache.addCache(identifier, sshConnect);
|
||||
}
|
||||
return clientSession;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-2
@@ -20,7 +20,6 @@ package org.apache.hertzbeat.collector.collect.telnet;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.ConnectException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -123,7 +122,7 @@ public class TelnetCollectImpl extends AbstractCollect {
|
||||
return new HashMap<>(16);
|
||||
}
|
||||
OutputStream outputStream = telnetClient.getOutputStream();
|
||||
outputStream.write(cmd.getBytes(StandardCharsets.UTF_8));
|
||||
outputStream.write(cmd.getBytes());
|
||||
outputStream.flush();
|
||||
String result = new String(telnetClient.getInputStream().readAllBytes());
|
||||
String[] lines = result.split("\n");
|
||||
|
||||
+9
-10
@@ -28,7 +28,6 @@ import java.net.Socket;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
@@ -129,16 +128,16 @@ public class WebsocketCollectImpl extends AbstractCollect {
|
||||
byte[] key = generateRandomKey();
|
||||
String base64Key = base64Encode(key);
|
||||
String requestLine = "GET " + websocketProtocol.getPath() + " HTTP/1.1\r\n";
|
||||
out.write(requestLine.getBytes(StandardCharsets.UTF_8));
|
||||
out.write(requestLine.getBytes());
|
||||
String hostName = InetAddress.getLocalHost().getHostAddress();
|
||||
out.write(("Host:" + hostName + "\r\n").getBytes(StandardCharsets.UTF_8));
|
||||
out.write("Upgrade: websocket\r\n".getBytes(StandardCharsets.UTF_8));
|
||||
out.write("Connection: Upgrade\r\n".getBytes(StandardCharsets.UTF_8));
|
||||
out.write("Sec-WebSocket-Version: 13\r\n".getBytes(StandardCharsets.UTF_8));
|
||||
out.write("Sec-WebSocket-Extensions: chat, superchat\r\n".getBytes(StandardCharsets.UTF_8));
|
||||
out.write(("Sec-WebSocket-Key: " + base64Key + "\r\n").getBytes(StandardCharsets.UTF_8));
|
||||
out.write("Content-Length: 0\r\n".getBytes(StandardCharsets.UTF_8));
|
||||
out.write("\r\n".getBytes(StandardCharsets.UTF_8));
|
||||
out.write(("Host:" + hostName + "\r\n").getBytes());
|
||||
out.write("Upgrade: websocket\r\n".getBytes());
|
||||
out.write("Connection: Upgrade\r\n".getBytes());
|
||||
out.write("Sec-WebSocket-Version: 13\r\n".getBytes());
|
||||
out.write("Sec-WebSocket-Extensions: chat, superchat\r\n".getBytes());
|
||||
out.write(("Sec-WebSocket-Key: " + base64Key + "\r\n").getBytes());
|
||||
out.write("Content-Length: 0\r\n".getBytes());
|
||||
out.write("\r\n".getBytes());
|
||||
out.flush();
|
||||
}
|
||||
|
||||
|
||||
+15
-15
@@ -20,7 +20,6 @@ package org.apache.hertzbeat.collector.collect.database;
|
||||
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.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
|
||||
import org.apache.hertzbeat.collector.dispatch.DispatchConstants;
|
||||
import org.apache.hertzbeat.common.entity.job.Metrics;
|
||||
@@ -100,27 +99,28 @@ class JdbcCommonCollectTest {
|
||||
"dm"
|
||||
};
|
||||
for (String platform : platforms) {
|
||||
assertDoesNotThrow(() -> {
|
||||
JdbcProtocol jdbc = new JdbcProtocol();
|
||||
jdbc.setPlatform(platform);
|
||||
|
||||
Metrics metrics = new Metrics();
|
||||
metrics.setJdbc(jdbc);
|
||||
|
||||
CollectRep.MetricsData.Builder builder = CollectRep.MetricsData.newBuilder();
|
||||
jdbcCommonCollect.collect(builder, metrics);
|
||||
});
|
||||
}
|
||||
// invalid platform
|
||||
assertThrows(IllegalArgumentException.class, () -> {
|
||||
JdbcProtocol jdbc = new JdbcProtocol();
|
||||
jdbc.setPlatform(platform);
|
||||
jdbc.setPlatform("invalid");
|
||||
|
||||
Metrics metrics = new Metrics();
|
||||
metrics.setJdbc(jdbc);
|
||||
|
||||
CollectRep.MetricsData.Builder builder = CollectRep.MetricsData.newBuilder();
|
||||
jdbcCommonCollect.collect(builder, metrics);
|
||||
assertNotEquals(builder.getMsg(), "Query Error: Not support database platform: " + platform);
|
||||
}
|
||||
// invalid platform
|
||||
JdbcProtocol jdbc = new JdbcProtocol();
|
||||
jdbc.setPlatform("invalid");
|
||||
|
||||
Metrics metrics = new Metrics();
|
||||
metrics.setJdbc(jdbc);
|
||||
|
||||
CollectRep.MetricsData.Builder builder = CollectRep.MetricsData.newBuilder();
|
||||
jdbcCommonCollect.collect(builder, metrics);
|
||||
assertEquals(builder.getCode(), CollectRep.Code.FAIL);
|
||||
assertEquals(builder.getMsg(), "Query Error: Not support database platform: invalid");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+31
-23
@@ -47,28 +47,31 @@ public class DnsCollectImplTest {
|
||||
.address("www.google.com")
|
||||
.timeout("3000")
|
||||
.port("53")
|
||||
.tcp("tcp")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPreCheck() {
|
||||
CollectRep.MetricsData.Builder builder = CollectRep.MetricsData.newBuilder();
|
||||
//metrics is null
|
||||
dnsCollect.collect(builder, null);
|
||||
assertEquals(CollectRep.Code.FAIL, builder.getCode());
|
||||
assertThrows(IllegalArgumentException.class, () -> {
|
||||
Metrics metrics = new Metrics();
|
||||
metrics.setName("question");
|
||||
metrics.setDns(dnsProtocol);
|
||||
dnsCollect.preCheck(metrics);
|
||||
});
|
||||
|
||||
//invalid DnsProtocol
|
||||
Metrics metrics = new Metrics();
|
||||
//query class is blank
|
||||
assertThrows(IllegalArgumentException.class, () -> {
|
||||
DnsProtocol dns = DnsProtocol.builder().build();
|
||||
|
||||
Metrics metrics = new Metrics();
|
||||
metrics.setDns(dns);
|
||||
dnsCollect.preCheck(metrics);
|
||||
});
|
||||
|
||||
//validated DnsProtocol
|
||||
// no exception throws
|
||||
assertDoesNotThrow(() -> {
|
||||
metrics.setName("question");
|
||||
dnsProtocol.setTcp("tcp");
|
||||
Metrics metrics = new Metrics();
|
||||
metrics.setDns(dnsProtocol);
|
||||
dnsCollect.preCheck(metrics);
|
||||
});
|
||||
@@ -77,23 +80,28 @@ public class DnsCollectImplTest {
|
||||
@Test
|
||||
public void testCollect() {
|
||||
CollectRep.MetricsData.Builder builder = CollectRep.MetricsData.newBuilder();
|
||||
Metrics metrics0 = Metrics.builder()
|
||||
.name("question")
|
||||
.dns(dnsProtocol)
|
||||
.aliasFields(Collections.singletonList("section"))
|
||||
.build();
|
||||
dnsCollect.collect(builder, metrics0);
|
||||
assertEquals(CollectRep.Code.SUCCESS, builder.getCode());
|
||||
long monitorId = 666;
|
||||
String app = "testDNS";
|
||||
Metrics metrics = new Metrics();
|
||||
metrics.setName("question");
|
||||
metrics.setDns(dnsProtocol);
|
||||
metrics.setAliasFields(Collections.singletonList("section"));
|
||||
dnsCollect.collect(builder, metrics);
|
||||
assertNotNull(builder.getValues(0).getColumns(0));
|
||||
|
||||
// dns is null, no exception throws
|
||||
assertDoesNotThrow(() -> {
|
||||
dnsCollect.collect(builder, null);
|
||||
});
|
||||
|
||||
// metric name is header
|
||||
Metrics metrics1 = Metrics.builder()
|
||||
.name("header")
|
||||
.dns(dnsProtocol)
|
||||
.aliasFields(Collections.singletonList("section"))
|
||||
.build();
|
||||
dnsCollect.collect(builder, metrics1);
|
||||
assertEquals(CollectRep.Code.SUCCESS, builder.getCode());
|
||||
assertDoesNotThrow(() -> {
|
||||
Metrics metrics1 = new Metrics();
|
||||
metrics1.setName("header");
|
||||
metrics1.setDns(dnsProtocol);
|
||||
metrics1.setAliasFields(Collections.singletonList("section"));
|
||||
dnsCollect.collect(builder, metrics1);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+1
-96
@@ -18,14 +18,7 @@
|
||||
package org.apache.hertzbeat.collector.collect.http;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
|
||||
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;
|
||||
@@ -48,7 +41,7 @@ class HttpCollectImplTest {
|
||||
assertThrows(IllegalArgumentException.class, () -> {
|
||||
httpCollectImpl.preCheck(null);
|
||||
});
|
||||
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> {
|
||||
Metrics metrics = Metrics.builder().build();
|
||||
httpCollectImpl.preCheck(metrics);
|
||||
@@ -72,92 +65,4 @@ class HttpCollectImplTest {
|
||||
String protocol = httpCollectImpl.supportProtocol();
|
||||
assert "http".equals(protocol);
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseResponseByXmlPath() throws Exception {
|
||||
// Create a sample XML response
|
||||
String xmlResponse = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<root>
|
||||
<server>
|
||||
<name>Server1</name>
|
||||
<status>Running</status>
|
||||
<metrics>
|
||||
<cpu>75.5</cpu>
|
||||
<memory>1024</memory>
|
||||
<disk>500</disk>
|
||||
</metrics>
|
||||
</server>
|
||||
<server>
|
||||
<name>Server2</name>
|
||||
<status>Stopped</status>
|
||||
<metrics>
|
||||
<cpu>0.0</cpu>
|
||||
<memory>0</memory>
|
||||
<disk>500</disk>
|
||||
</metrics>
|
||||
</server>
|
||||
</root>
|
||||
""";
|
||||
|
||||
// Set up HttpProtocol with XML path parsing
|
||||
HttpProtocol http = HttpProtocol.builder()
|
||||
.parseType(DispatchConstants.PARSE_XML_PATH)
|
||||
.parseScript("//server") // XPath to select all server nodes
|
||||
.build();
|
||||
|
||||
// Set up Metrics with fields that have XPath expressions
|
||||
List<Metrics.Field> fields = new ArrayList<>();
|
||||
fields.add(Metrics.Field.builder().field("name").build());
|
||||
fields.add(Metrics.Field.builder().field("status").build());
|
||||
fields.add(Metrics.Field.builder().field("metrics/cpu").build());
|
||||
fields.add(Metrics.Field.builder().field("metrics/memory").build());
|
||||
|
||||
Metrics metrics = Metrics.builder()
|
||||
.http(http)
|
||||
.fields(fields)
|
||||
.aliasFields(Arrays.asList("name", "status", "metrics/cpu", "metrics/memory"))
|
||||
.build();
|
||||
|
||||
// Create a custom builder that captures added rows
|
||||
List<CollectRep.ValueRow> capturedRows = new ArrayList<>();
|
||||
CollectRep.MetricsData.Builder builder = new CollectRep.MetricsData.Builder() {
|
||||
@Override
|
||||
public CollectRep.MetricsData.Builder addValueRow(CollectRep.ValueRow valueRow) {
|
||||
capturedRows.add(valueRow);
|
||||
return super.addValueRow(valueRow);
|
||||
}
|
||||
};
|
||||
|
||||
// Use reflection to access the private parseResponseByXmlPath method
|
||||
Method parseMethod = HttpCollectImpl.class.getDeclaredMethod(
|
||||
"parseResponseByXmlPath",
|
||||
String.class,
|
||||
Metrics.class,
|
||||
CollectRep.MetricsData.Builder.class,
|
||||
Long.class);
|
||||
parseMethod.setAccessible(true);
|
||||
|
||||
// Call the method
|
||||
parseMethod.invoke(httpCollectImpl, xmlResponse, metrics, builder, 100L);
|
||||
|
||||
// Verify the results
|
||||
assertEquals(2, capturedRows.size(), "Should have parsed 2 server nodes");
|
||||
|
||||
// Check first server
|
||||
CollectRep.ValueRow firstRow = capturedRows.get(0);
|
||||
assertEquals(4, firstRow.getColumnsCount(), "First row should have 4 columns");
|
||||
assertEquals("Server1", firstRow.getColumns(0), "First server name should be Server1");
|
||||
assertEquals("Running", firstRow.getColumns(1), "First server status should be Running");
|
||||
assertEquals("75.5", firstRow.getColumns(2), "First server CPU should be 75.5");
|
||||
assertEquals("1024", firstRow.getColumns(3), "First server memory should be 1024");
|
||||
|
||||
// Check second server
|
||||
CollectRep.ValueRow secondRow = capturedRows.get(1);
|
||||
assertEquals(4, secondRow.getColumnsCount(), "Second row should have 4 columns");
|
||||
assertEquals("Server2", secondRow.getColumns(0), "Second server name should be Server2");
|
||||
assertEquals("Stopped", secondRow.getColumns(1), "Second server status should be Stopped");
|
||||
assertEquals("0.0", secondRow.getColumns(2), "Second server CPU should be 0.0");
|
||||
assertEquals("0", secondRow.getColumns(3), "Second server memory should be 0");
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user