mirror of
https://github.com/dromara/hertzbeat.git
synced 2026-09-17 18:19:02 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b9f1d65cb5 | ||
|
|
1dea5467fc |
+1
-47
@@ -2545,8 +2545,7 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/176664901?v=4",
|
||||
"profile": "https://github.com/Saramanda9988",
|
||||
"contributions": [
|
||||
"doc",
|
||||
"code"
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -2668,51 +2667,6 @@
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "KOYR",
|
||||
"name": "KOYR",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/53216619?v=4",
|
||||
"profile": "https://github.com/KOYR",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Lathika226",
|
||||
"name": "LathikaBaddam",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/178710568?v=4",
|
||||
"profile": "https://github.com/Lathika226",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Sahil-Shadwal",
|
||||
"name": "Sahil Shadwal",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/119167601?v=4",
|
||||
"profile": "https://shadwal.space/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "BhanuNidumolu",
|
||||
"name": "N.Bhanu Prasad",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/180380413?v=4",
|
||||
"profile": "https://github.com/BhanuNidumolu",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Prakash1185",
|
||||
"name": "Prakash Kumar",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/183058331?v=4",
|
||||
"profile": "https://prakashh-portfolio.vercel.app/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
||||
@@ -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.
|
||||
#
|
||||
|
||||
# EditorConfig is awesome: https://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_style = space
|
||||
# The line max length is 120, match checkstyle rules.
|
||||
max_line_length = 120
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.json]
|
||||
tab_width = 2
|
||||
indent_size = 2
|
||||
|
||||
[*.{yml,yaml}]
|
||||
tab_width = 2
|
||||
indent_size = 2
|
||||
|
||||
[*.xml]
|
||||
ij_xml_attribute_wrap = off
|
||||
ij_xml_text_wrap = off
|
||||
ij_xml_keep_blank_lines = 1
|
||||
|
||||
[pom.xml]
|
||||
indent_size = 2
|
||||
|
||||
[*.java]
|
||||
indent_size = 4
|
||||
tab_width = 4
|
||||
|
||||
[*.js]
|
||||
indent_size = 2
|
||||
|
||||
[*.ts]
|
||||
indent_size = 2
|
||||
@@ -1,82 +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.
|
||||
|
||||
name: DOC Deploy
|
||||
|
||||
on:
|
||||
# Trigger on push to master branch when home directory files are changed
|
||||
push:
|
||||
branches: [ master ]
|
||||
paths:
|
||||
- 'home/**'
|
||||
|
||||
# Allow manual workflow trigger
|
||||
workflow_dispatch:
|
||||
|
||||
# Set permissions for deployment
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
name: Deploy Documentation to asf-site Branch
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
# Checkout the repository code
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# Setup Node.js environment
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
# Install dependencies in home directory
|
||||
- name: Install Dependencies
|
||||
working-directory: home
|
||||
run: npm ci
|
||||
|
||||
# Build the documentation site
|
||||
- name: Build Documentation
|
||||
working-directory: home
|
||||
run: npm run build
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=4096
|
||||
|
||||
# Deploy to asf-site branch using peaceiris/actions-gh-pages
|
||||
- name: Deploy to asf-site Branch
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./home/build
|
||||
publish_branch: asf-site
|
||||
force_orphan: true
|
||||
user_name: 'github-actions[bot]'
|
||||
user_email: 'github-actions[bot]@users.noreply.github.com'
|
||||
commit_message: 'Deploy documentation to asf-site branch'
|
||||
|
||||
# Output deployment status
|
||||
- name: Deployment Summary
|
||||
run: |
|
||||
echo "Documentation deployed successfully!"
|
||||
echo "Deployment branch: asf-site"
|
||||
echo "Source branch: ${{ github.ref_name }}"
|
||||
echo "Commit SHA: ${{ github.sha }}"
|
||||
@@ -39,7 +39,6 @@ header:
|
||||
- '**/*.js'
|
||||
- '**/*.less'
|
||||
- '**/*.txt'
|
||||
- '**/*.st'
|
||||
- '**/target/**'
|
||||
- '.gitattributes'
|
||||
- '**/.gitignore'
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"MD025": true,
|
||||
"MD029": true,
|
||||
"MD033": {
|
||||
"allowed_elements": ["table", "tr", "td", "a", "img", "sub", "b", "br", "img", "tbody", "mark", "font", "Tabs", "TabItem"]
|
||||
"allowed_elements": ["table", "tr", "td", "a", "img", "sub", "b", "br", "img", "tbody", "mark", "font"]
|
||||
},
|
||||
"MD036": false,
|
||||
"MD040": true,
|
||||
|
||||
@@ -1,22 +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.
|
||||
|
||||
# Testcontainers configuration
|
||||
# Disable Ryuk container reuse to avoid issues in CI
|
||||
testcontainers.reuse.enable=false
|
||||
# Configure Docker host detection
|
||||
testcontainers.docker.client.strategy=org.testcontainers.dockerclient.UnixSocketClientProviderStrategy
|
||||
@@ -525,7 +525,7 @@ Thanks to these wonderful people, welcome to join us:
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/lx1229"><img src="https://avatars.githubusercontent.com/u/44620005?v=4?s=100" width="100px;" alt="Liuxin"/><br /><sub><b>Liuxin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=lx1229" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yy549159265"><img src="https://avatars.githubusercontent.com/u/40821310?v=4?s=100" width="100px;" alt="yy549159265"/><br /><sub><b>yy549159265</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yy549159265" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=yy549159265" title="Tests">⚠️</a> <a href="#design-yy549159265" title="Design">🎨</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/cto-huhang"><img src="https://avatars.githubusercontent.com/u/53338629?v=4?s=100" width="100px;" alt="cto-huhang"/><br /><sub><b>cto-huhang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=cto-huhang" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Saramanda9988"><img src="https://avatars.githubusercontent.com/u/176664901?v=4?s=100" width="100px;" alt="LunaRain_079"/><br /><sub><b>LunaRain_079</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Saramanda9988" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/commits?author=Saramanda9988" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Saramanda9988"><img src="https://avatars.githubusercontent.com/u/176664901?v=4?s=100" width="100px;" alt="LunaRain_079"/><br /><sub><b>LunaRain_079</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Saramanda9988" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/delei"><img src="https://avatars.githubusercontent.com/u/17263766?v=4?s=100" width="100px;" alt="DeleiGuo"/><br /><sub><b>DeleiGuo</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=delei" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/commits?author=delei" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=delei" title="Tests">⚠️</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Adelei" title="Bug reports">🐛</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -543,13 +543,6 @@ Thanks to these wonderful people, welcome to join us:
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Jetiaime"><img src="https://avatars.githubusercontent.com/u/93769000?v=4?s=100" width="100px;" alt="TeAmo"/><br /><sub><b>TeAmo</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Jetiaime" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/pentium100"><img src="https://avatars.githubusercontent.com/u/27917?v=4?s=100" width="100px;" alt="pentium100"/><br /><sub><b>pentium100</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=pentium100" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/dedyks"><img src="https://avatars.githubusercontent.com/u/23741665?v=4?s=100" width="100px;" alt="Dedy Kurniawan Santoso"/><br /><sub><b>Dedy Kurniawan Santoso</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=dedyks" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/KOYR"><img src="https://avatars.githubusercontent.com/u/53216619?v=4?s=100" width="100px;" alt="KOYR"/><br /><sub><b>KOYR</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=KOYR" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Lathika226"><img src="https://avatars.githubusercontent.com/u/178710568?v=4?s=100" width="100px;" alt="LathikaBaddam"/><br /><sub><b>LathikaBaddam</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Lathika226" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://shadwal.space/"><img src="https://avatars.githubusercontent.com/u/119167601?v=4?s=100" width="100px;" alt="Sahil Shadwal"/><br /><sub><b>Sahil Shadwal</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Sahil-Shadwal" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/BhanuNidumolu"><img src="https://avatars.githubusercontent.com/u/180380413?v=4?s=100" width="100px;" alt="N.Bhanu Prasad"/><br /><sub><b>N.Bhanu Prasad</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=BhanuNidumolu" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://prakashh-portfolio.vercel.app/"><img src="https://avatars.githubusercontent.com/u/183058331?v=4?s=100" width="100px;" alt="Prakash Kumar"/><br /><sub><b>Prakash Kumar</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Prakash1185" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
+1
-8
@@ -522,7 +522,7 @@ Thanks these wonderful people, welcome to join us:
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/lx1229"><img src="https://avatars.githubusercontent.com/u/44620005?v=4?s=100" width="100px;" alt="Liuxin"/><br /><sub><b>Liuxin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=lx1229" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yy549159265"><img src="https://avatars.githubusercontent.com/u/40821310?v=4?s=100" width="100px;" alt="yy549159265"/><br /><sub><b>yy549159265</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yy549159265" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=yy549159265" title="Tests">⚠️</a> <a href="#design-yy549159265" title="Design">🎨</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/cto-huhang"><img src="https://avatars.githubusercontent.com/u/53338629?v=4?s=100" width="100px;" alt="cto-huhang"/><br /><sub><b>cto-huhang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=cto-huhang" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Saramanda9988"><img src="https://avatars.githubusercontent.com/u/176664901?v=4?s=100" width="100px;" alt="LunaRain_079"/><br /><sub><b>LunaRain_079</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Saramanda9988" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/commits?author=Saramanda9988" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Saramanda9988"><img src="https://avatars.githubusercontent.com/u/176664901?v=4?s=100" width="100px;" alt="LunaRain_079"/><br /><sub><b>LunaRain_079</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Saramanda9988" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/delei"><img src="https://avatars.githubusercontent.com/u/17263766?v=4?s=100" width="100px;" alt="DeleiGuo"/><br /><sub><b>DeleiGuo</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=delei" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/commits?author=delei" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=delei" title="Tests">⚠️</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Adelei" title="Bug reports">🐛</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -540,13 +540,6 @@ Thanks these wonderful people, welcome to join us:
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Jetiaime"><img src="https://avatars.githubusercontent.com/u/93769000?v=4?s=100" width="100px;" alt="TeAmo"/><br /><sub><b>TeAmo</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Jetiaime" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/pentium100"><img src="https://avatars.githubusercontent.com/u/27917?v=4?s=100" width="100px;" alt="pentium100"/><br /><sub><b>pentium100</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=pentium100" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/dedyks"><img src="https://avatars.githubusercontent.com/u/23741665?v=4?s=100" width="100px;" alt="Dedy Kurniawan Santoso"/><br /><sub><b>Dedy Kurniawan Santoso</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=dedyks" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/KOYR"><img src="https://avatars.githubusercontent.com/u/53216619?v=4?s=100" width="100px;" alt="KOYR"/><br /><sub><b>KOYR</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=KOYR" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Lathika226"><img src="https://avatars.githubusercontent.com/u/178710568?v=4?s=100" width="100px;" alt="LathikaBaddam"/><br /><sub><b>LathikaBaddam</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Lathika226" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://shadwal.space/"><img src="https://avatars.githubusercontent.com/u/119167601?v=4?s=100" width="100px;" alt="Sahil Shadwal"/><br /><sub><b>Sahil Shadwal</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Sahil-Shadwal" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/BhanuNidumolu"><img src="https://avatars.githubusercontent.com/u/180380413?v=4?s=100" width="100px;" alt="N.Bhanu Prasad"/><br /><sub><b>N.Bhanu Prasad</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=BhanuNidumolu" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://prakashh-portfolio.vercel.app/"><img src="https://avatars.githubusercontent.com/u/183058331?v=4?s=100" width="100px;" alt="Prakash Kumar"/><br /><sub><b>Prakash Kumar</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Prakash1185" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
+1
-8
@@ -525,7 +525,7 @@ Thanks these wonderful people, welcome to join us:
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/lx1229"><img src="https://avatars.githubusercontent.com/u/44620005?v=4?s=100" width="100px;" alt="Liuxin"/><br /><sub><b>Liuxin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=lx1229" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yy549159265"><img src="https://avatars.githubusercontent.com/u/40821310?v=4?s=100" width="100px;" alt="yy549159265"/><br /><sub><b>yy549159265</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yy549159265" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=yy549159265" title="Tests">⚠️</a> <a href="#design-yy549159265" title="Design">🎨</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/cto-huhang"><img src="https://avatars.githubusercontent.com/u/53338629?v=4?s=100" width="100px;" alt="cto-huhang"/><br /><sub><b>cto-huhang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=cto-huhang" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Saramanda9988"><img src="https://avatars.githubusercontent.com/u/176664901?v=4?s=100" width="100px;" alt="LunaRain_079"/><br /><sub><b>LunaRain_079</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Saramanda9988" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/commits?author=Saramanda9988" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Saramanda9988"><img src="https://avatars.githubusercontent.com/u/176664901?v=4?s=100" width="100px;" alt="LunaRain_079"/><br /><sub><b>LunaRain_079</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Saramanda9988" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/delei"><img src="https://avatars.githubusercontent.com/u/17263766?v=4?s=100" width="100px;" alt="DeleiGuo"/><br /><sub><b>DeleiGuo</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=delei" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/commits?author=delei" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=delei" title="Tests">⚠️</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Adelei" title="Bug reports">🐛</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -543,13 +543,6 @@ Thanks these wonderful people, welcome to join us:
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Jetiaime"><img src="https://avatars.githubusercontent.com/u/93769000?v=4?s=100" width="100px;" alt="TeAmo"/><br /><sub><b>TeAmo</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Jetiaime" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/pentium100"><img src="https://avatars.githubusercontent.com/u/27917?v=4?s=100" width="100px;" alt="pentium100"/><br /><sub><b>pentium100</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=pentium100" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/dedyks"><img src="https://avatars.githubusercontent.com/u/23741665?v=4?s=100" width="100px;" alt="Dedy Kurniawan Santoso"/><br /><sub><b>Dedy Kurniawan Santoso</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=dedyks" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/KOYR"><img src="https://avatars.githubusercontent.com/u/53216619?v=4?s=100" width="100px;" alt="KOYR"/><br /><sub><b>KOYR</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=KOYR" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Lathika226"><img src="https://avatars.githubusercontent.com/u/178710568?v=4?s=100" width="100px;" alt="LathikaBaddam"/><br /><sub><b>LathikaBaddam</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Lathika226" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://shadwal.space/"><img src="https://avatars.githubusercontent.com/u/119167601?v=4?s=100" width="100px;" alt="Sahil Shadwal"/><br /><sub><b>Sahil Shadwal</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Sahil-Shadwal" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/BhanuNidumolu"><img src="https://avatars.githubusercontent.com/u/180380413?v=4?s=100" width="100px;" alt="N.Bhanu Prasad"/><br /><sub><b>N.Bhanu Prasad</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=BhanuNidumolu" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://prakashh-portfolio.vercel.app/"><img src="https://avatars.githubusercontent.com/u/183058331?v=4?s=100" width="100px;" alt="Prakash Kumar"/><br /><sub><b>Prakash Kumar</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Prakash1185" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"intervals": 60,
|
||||
"tags": [],
|
||||
"app": "ftp",
|
||||
"instance": "127.0.0.1",
|
||||
"host": "127.0.0.1",
|
||||
"name": "{{.param.monitorFTP}}"
|
||||
},
|
||||
"params": [
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"intervals": 60,
|
||||
"tags": [],
|
||||
"app": "api",
|
||||
"instance": "127.0.0.1",
|
||||
"host": "127.0.0.1",
|
||||
"name": "{{.param.monitorHTTP}}"
|
||||
},
|
||||
"params": [
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"intervals": 60,
|
||||
"tags": [],
|
||||
"app": "ping",
|
||||
"instance": "127.0.0.1",
|
||||
"host": "127.0.0.1",
|
||||
"name": "{{.param.monitorPing}}"
|
||||
},
|
||||
"params": [
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"intervals": 60,
|
||||
"tags": [],
|
||||
"app": "port",
|
||||
"instance": "127.0.0.1",
|
||||
"host": "127.0.0.1",
|
||||
"name": "{{.param.monitorPort}}"
|
||||
},
|
||||
"params": [
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"intervals": 60,
|
||||
"tags": [],
|
||||
"app": "fullsite",
|
||||
"instance": "127.0.0.1",
|
||||
"host": "127.0.0.1",
|
||||
"name": "{{.param.monitorSiteMap}}"
|
||||
},
|
||||
"params": [
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"intervals": 60,
|
||||
"tags": [],
|
||||
"app": "ssl_cert",
|
||||
"instance": "127.0.0.1",
|
||||
"host": "127.0.0.1",
|
||||
"name": "{{.param.monitorSSL}}"
|
||||
},
|
||||
"params": [
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"intervals": 60,
|
||||
"tags": [],
|
||||
"app": "udp_port",
|
||||
"instance": "127.0.0.1",
|
||||
"host": "127.0.0.1",
|
||||
"name": "{{.param.monitorUDP}}"
|
||||
},
|
||||
"params": [
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"intervals": 60,
|
||||
"tags": [],
|
||||
"app": "website",
|
||||
"instance": "127.0.0.1",
|
||||
"host": "127.0.0.1",
|
||||
"name": "{{.param.monitorWebsite}}"
|
||||
},
|
||||
"params": [
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"intervals": 10,
|
||||
"tags": [],
|
||||
"app": "kubernetes",
|
||||
"instance": "172.29.0.11",
|
||||
"host": "172.29.0.11",
|
||||
"name": "Brave_Stingray_55yR"
|
||||
},
|
||||
"collector": "",
|
||||
|
||||
@@ -26,8 +26,8 @@
|
||||
<artifactId>hertzbeat-ai</artifactId>
|
||||
<version>${hertzbeat.version}</version>
|
||||
<properties>
|
||||
<spring-ai.version>1.1.1</spring-ai.version>
|
||||
<java.version>17</java.version>
|
||||
<spring-ai.version>1.0.3</spring-ai.version>
|
||||
<java.version>21</java.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
+246
@@ -0,0 +1,246 @@
|
||||
/*
|
||||
* 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.ai.config;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.usthe.sureness.mgt.SurenessSecurityManager;
|
||||
import com.usthe.sureness.subject.SubjectSum;
|
||||
import io.modelcontextprotocol.spec.McpError;
|
||||
import io.modelcontextprotocol.spec.McpSchema;
|
||||
import io.modelcontextprotocol.spec.McpServerSession;
|
||||
import io.modelcontextprotocol.spec.McpServerTransport;
|
||||
import io.modelcontextprotocol.spec.McpServerTransportProvider;
|
||||
import io.modelcontextprotocol.util.Assert;
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.servlet.function.RouterFunction;
|
||||
import org.springframework.web.servlet.function.RouterFunctions;
|
||||
import org.springframework.web.servlet.function.ServerRequest;
|
||||
import org.springframework.web.servlet.function.ServerResponse;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* Custom Server-Sent Events transport provider for Model Context Protocol.
|
||||
*/
|
||||
@Slf4j
|
||||
public class CustomSseServerTransport implements McpServerTransportProvider {
|
||||
private final ObjectMapper objectMapper;
|
||||
private final String messageEndpoint;
|
||||
private final String sseEndpoint;
|
||||
private final String baseUrl;
|
||||
@Getter
|
||||
private final RouterFunction<ServerResponse> routerFunction;
|
||||
@Setter
|
||||
private McpServerSession.Factory sessionFactory;
|
||||
private final Map<String, Object> sessionRequest = new HashMap<>();
|
||||
private final ConcurrentHashMap<String, McpServerSession> sessions;
|
||||
private volatile boolean isClosing;
|
||||
|
||||
public CustomSseServerTransport(ObjectMapper objectMapper, String messageEndpoint) {
|
||||
this(objectMapper, messageEndpoint, "/sse");
|
||||
}
|
||||
|
||||
|
||||
public CustomSseServerTransport(ObjectMapper objectMapper, String messageEndpoint, String sseEndpoint) {
|
||||
this(objectMapper, "", messageEndpoint, sseEndpoint);
|
||||
}
|
||||
|
||||
public CustomSseServerTransport(ObjectMapper objectMapper, String baseUrl, String messageEndpoint, String sseEndpoint) {
|
||||
this.sessions = new ConcurrentHashMap();
|
||||
this.isClosing = false;
|
||||
Assert.notNull(objectMapper, "ObjectMapper must not be null");
|
||||
Assert.notNull(baseUrl, "Message base URL must not be null");
|
||||
Assert.notNull(messageEndpoint, "Message endpoint must not be null");
|
||||
Assert.notNull(sseEndpoint, "SSE endpoint must not be null");
|
||||
this.objectMapper = objectMapper;
|
||||
this.baseUrl = baseUrl;
|
||||
this.messageEndpoint = messageEndpoint;
|
||||
this.sseEndpoint = sseEndpoint;
|
||||
this.routerFunction = RouterFunctions.route().GET(this.sseEndpoint, this::handleSseConnection).POST(this.messageEndpoint, this::handleMessage).build();
|
||||
}
|
||||
|
||||
public Mono<Void> notifyClients(String method, Object params) {
|
||||
if (this.sessions.isEmpty()) {
|
||||
log.debug("No active sessions to broadcast message to");
|
||||
return Mono.empty();
|
||||
} else {
|
||||
log.debug("Attempting to broadcast message to {} active sessions", this.sessions.size());
|
||||
return Flux.fromIterable(this.sessions.values())
|
||||
.flatMap((session) -> session.sendNotification(method, params)
|
||||
.doOnError((e) -> log.error("Failed to send message to session {}: {}", session.getId(), e.getMessage()))
|
||||
.onErrorComplete())
|
||||
.then();
|
||||
}
|
||||
}
|
||||
|
||||
public Mono<Void> closeGracefully() {
|
||||
return Flux.fromIterable(this.sessions.values()).doFirst(() -> {
|
||||
this.isClosing = true;
|
||||
log.debug("Initiating graceful shutdown with {} active sessions", this.sessions.size());
|
||||
}).flatMap(McpServerSession::closeGracefully).then().doOnSuccess((v) -> log.debug("Graceful shutdown completed"));
|
||||
}
|
||||
|
||||
private ServerResponse handleSseConnection(ServerRequest request) {
|
||||
log.debug("Handling SSE connection for request: {}", request);
|
||||
HttpServletRequest servletRequest = request.servletRequest();
|
||||
|
||||
try {
|
||||
|
||||
log.debug("Processing SSE connection for servlet request: {}", servletRequest);
|
||||
log.debug("Authorization header: {}", servletRequest.getHeader("Authorization"));
|
||||
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Authentication failed for SSE connection: {}", e.getMessage());
|
||||
return ServerResponse.status(HttpStatus.UNAUTHORIZED).body("Unauthorized: " + e.getMessage());
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (this.isClosing) {
|
||||
return ServerResponse.status(HttpStatus.SERVICE_UNAVAILABLE).body("Server is shutting down");
|
||||
} else {
|
||||
String sessionId = UUID.randomUUID().toString();
|
||||
log.debug("Generated session ID for SSE connection: {}", sessionId);
|
||||
log.debug("Creating new SSE connection for session: {}", sessionId);
|
||||
|
||||
|
||||
return ServerResponse.sse((sseBuilder) -> {
|
||||
sseBuilder.onComplete(() -> {
|
||||
log.debug("SSE connection completed for session: {}", sessionId);
|
||||
this.sessions.remove(sessionId);
|
||||
});
|
||||
sseBuilder.onTimeout(() -> {
|
||||
log.debug("SSE connection timed out for session: {}", sessionId);
|
||||
this.sessions.remove(sessionId);
|
||||
});
|
||||
CustomSseServerTransport.WebMvcMcpSessionTransport sessionTransport = new CustomSseServerTransport.WebMvcMcpSessionTransport(sessionId, sseBuilder);
|
||||
McpServerSession session = this.sessionFactory.create(sessionTransport);
|
||||
this.sessionRequest.put(sessionId, request.servletRequest());
|
||||
this.sessions.put(sessionId, session);
|
||||
|
||||
try {
|
||||
sseBuilder.id(sessionId).event("endpoint").data(this.baseUrl + this.messageEndpoint + "?sessionId=" + sessionId);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to send initial endpoint event: {}", e.getMessage());
|
||||
sseBuilder.error(e);
|
||||
}
|
||||
|
||||
}, Duration.ZERO);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private ServerResponse handleMessage(ServerRequest request) {
|
||||
if (this.isClosing) {
|
||||
return ServerResponse.status(HttpStatus.SERVICE_UNAVAILABLE).body("Server is shutting down");
|
||||
} else if (request.param("sessionId").isEmpty()) {
|
||||
return ServerResponse.badRequest().body(new McpError("Session ID missing in message endpoint"));
|
||||
} else {
|
||||
String sessionId = (String) request.param("sessionId").get();
|
||||
McpServerSession session = (McpServerSession) this.sessions.get(sessionId);
|
||||
log.debug("Authorization header for message request: {}", request.servletRequest().getHeader("Authorization"));
|
||||
SubjectSum subject = SurenessSecurityManager.getInstance().checkIn(sessionRequest.get(sessionId));
|
||||
McpContextHolder.setSubject(subject);
|
||||
|
||||
|
||||
if (session == null) {
|
||||
return ServerResponse.status(HttpStatus.NOT_FOUND).body(new McpError("Session not found: " + sessionId));
|
||||
} else {
|
||||
try {
|
||||
String body = request.body(String.class);
|
||||
McpSchema.JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(this.objectMapper, body);
|
||||
session.handle(message).block();
|
||||
return ServerResponse.ok().build();
|
||||
} catch (IOException | IllegalArgumentException e) {
|
||||
log.error("Failed to deserialize message: {}", ((Exception) e).getMessage());
|
||||
return ServerResponse.badRequest().body(new McpError("Invalid message format"));
|
||||
} catch (Exception e) {
|
||||
log.error("Error handling message: {}", e.getMessage());
|
||||
return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR).body(new McpError(e.getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class WebMvcMcpSessionTransport implements McpServerTransport {
|
||||
private final String sessionId;
|
||||
private final ServerResponse.SseBuilder sseBuilder;
|
||||
|
||||
WebMvcMcpSessionTransport(String sessionId, ServerResponse.SseBuilder sseBuilder) {
|
||||
this.sessionId = sessionId;
|
||||
this.sseBuilder = sseBuilder;
|
||||
log.debug("Session transport {} initialized with SSE builder", sessionId);
|
||||
}
|
||||
|
||||
public Mono<Void> sendMessage(McpSchema.JSONRPCMessage message) {
|
||||
return Mono.fromRunnable(() -> {
|
||||
try {
|
||||
String jsonText = CustomSseServerTransport.this.objectMapper.writeValueAsString(message);
|
||||
this.sseBuilder.id(this.sessionId).event("message").data(jsonText);
|
||||
log.debug("Message sent to session {}", this.sessionId);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to send message to session {}: {}", this.sessionId, e.getMessage());
|
||||
this.sseBuilder.error(e);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
public <T> T unmarshalFrom(Object data, TypeReference<T> typeRef) {
|
||||
return (T) CustomSseServerTransport.this.objectMapper.convertValue(data, typeRef);
|
||||
}
|
||||
|
||||
public Mono<Void> closeGracefully() {
|
||||
return Mono.fromRunnable(() -> {
|
||||
log.debug("Closing session transport: {}", this.sessionId);
|
||||
|
||||
try {
|
||||
this.sseBuilder.complete();
|
||||
log.debug("Successfully completed SSE builder for session {}", this.sessionId);
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to complete SSE builder for session {}: {}", this.sessionId, e.getMessage());
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
public void close() {
|
||||
try {
|
||||
this.sseBuilder.complete();
|
||||
log.debug("Successfully completed SSE builder for session {}", this.sessionId);
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to complete SSE builder for session {}: {}", this.sessionId, e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,8 +33,8 @@ public final class McpContextHolder {
|
||||
* Attaches the user's context to the current thread.
|
||||
*/
|
||||
public static void setSubject(SubjectSum subject) {
|
||||
|
||||
subjectHolder.set(subject);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
* 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.ai.config;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Provider for system prompts used in the AI agent
|
||||
*/
|
||||
|
||||
@Component
|
||||
public class PromptProvider {
|
||||
/**
|
||||
* Static version of the HertzBeat monitoring prompt
|
||||
*/
|
||||
public static final String HERTZBEAT_SYSTEM_PROMPT = """
|
||||
You are an AI Assistant specialized in monitoring infrastructure and applications with HertzBeat.
|
||||
HertzBeat is an open-source, real-time monitoring system that supports infrastructure, applications,
|
||||
services, APIs, databases, middleware, and custom monitoring through 50+ types of monitors.
|
||||
Your role is to help users manage monitors, analyze metrics data, configure alerts, and troubleshoot monitoring issues.
|
||||
*******
|
||||
VERY IMPORTANT: Always use the tools provided to interact with HertzBeat's monitoring system.
|
||||
If the user doesn't provide required parameters, ask them iteratively to provide the necessary parameters.
|
||||
********
|
||||
|
||||
## Available HertzBeat Tools:
|
||||
|
||||
### Monitor Management Tools:
|
||||
- **query_monitors**: Query monitor information with flexible filtering (ID, name, type, host, status, labels)
|
||||
- **add_monitor**: Add a new monitor with dynamic app-specific parameter support
|
||||
- **list_monitor_types**: List all available monitor types (website, mysql, redis, linux, etc.)
|
||||
- **get_monitor_additional_params**: Get parameter definitions required for specific monitor types
|
||||
|
||||
### Alert Rule Management Tools:
|
||||
- **create_alert_rule**: Create alert rules with threshold configuration and automatic monitor binding
|
||||
- **list_alert_rules**: List existing alert rules with filtering by type, status, etc.
|
||||
- **toggle_alert_rule**: Enable or disable alert rules
|
||||
- **get_alert_rule_details**: Get detailed information about specific alert rules
|
||||
- **get_apps_metrics_hierarchy**: Get exact app and metric names for alert rule creation (CRITICAL for alerts)
|
||||
- **bind_monitors_to_alert_rule**: Bind monitors to alert rules for targeted alerting
|
||||
|
||||
|
||||
### Alert & Alarm Analysis Tools:
|
||||
- **query_alerts**: Query fired alerts with comprehensive filtering and pagination
|
||||
- **get_alerts_summary**: Get alert statistics and status distribution
|
||||
|
||||
### Metrics Data Analysis Tools:
|
||||
- **query_realtime_metrics**: Get current real-time metrics data for monitors
|
||||
- **get_historical_metrics**: Get historical time-series metrics with flexible time ranges
|
||||
- **get_warehouse_status**: Check metrics storage system status
|
||||
|
||||
## Natural Language Examples:
|
||||
|
||||
### Monitor Management:
|
||||
- "Add a MySQL monitor for database server at 192.168.1.10 with user admin"
|
||||
- "Monitor website https://example.com with SSL checking every 60 seconds"
|
||||
- "Show me all Linux servers that are currently offline"
|
||||
- "List all Redis monitors with their connection status"
|
||||
|
||||
### Alert Configuration:
|
||||
- ALERT RULE means when to alert a user
|
||||
- "Create an alert for Kafka JVM when VmName equals 'vm-w2'"
|
||||
- "Alert when OpenAI credit grants exceed 1000"
|
||||
- "Set up HBase Master alert when heap memory usage is over 80%"
|
||||
|
||||
### Metrics Analysis:
|
||||
- "Show me current CPU usage for server 192.168.1.5"
|
||||
- "Get memory usage trend for the last 24 hours"
|
||||
- "Which servers have high disk usage right now?"
|
||||
- "Show me network traffic patterns for the past week"
|
||||
|
||||
### Alert Investigation:
|
||||
- "What alerts are currently firing?"
|
||||
- "Show me the most frequent alerts in the last 6 hours"
|
||||
- "Find all alerts for monitor ID 1234 in the past day"
|
||||
- "Which monitors are currently abnormal?"
|
||||
|
||||
|
||||
## Workflow Guidelines:
|
||||
|
||||
1. **Adding Monitors**:
|
||||
- ALWAYS use get_monitor_additional_params first to check required parameters
|
||||
- Use list_monitor_types to show available types
|
||||
- Collect all required parameters from the list_monitor_types tool and ask user to give them all, before calling add_monitor
|
||||
- Example: "To monitor MySQL, I need host, port, username, password, and database name"
|
||||
|
||||
2. **Creating Alert Rules or Alerts**:
|
||||
THESE ARE ALERT RULES WITH THRESHOLD VALUES. USERS CAN SPECIFY THE THRESHOLD VALUES FOR EXAMPLE,
|
||||
IF THE USER SAYS "ALERT ME WHEN MY COST EXCEEDS 700, THE EXPRESSION SHOULD BE 'cost > 700' NOT 'cost < 700'.
|
||||
APPLY THE SAME LOGIC FOR LESS THAN OPERATOR.
|
||||
It is important to first understand the hierarchy of apps, metrics, and field conditions
|
||||
Each app has its own metrics and each metric has its own field conditions.
|
||||
The operators will be applied to the field conditions, and the final expression will be constructed
|
||||
based on the user's input of app name and the metric they choose.
|
||||
Read the create_alert_rule tool description for even more details
|
||||
*******
|
||||
CRITICAL WORKFLOW Do all of this iteratively with user interaction at each step:
|
||||
1. ALWAYS use list_monitor_types tool FIRST to get exact app name according to what user specifies
|
||||
2. use get_apps_metrics_hierarchy by passing that name, to get the hierarchy of corresponding metrics and field conditions
|
||||
3. Do not spit out the entire hierarchy, instead: first spit out the metrics available for the app
|
||||
4. Ask the user to choose a metric from the available metrics
|
||||
5. Based on the metric chosen, present the available field conditions
|
||||
6. You will construct the proper expression with field conditions
|
||||
VERY VERY IMPORTANT:
|
||||
- ALWAYS USE the value field from the get_apps_metrics_hierarchy's json response when creating alert expressions on the field parameters
|
||||
*********
|
||||
|
||||
- Field Condition Expression format: [field_conditions]
|
||||
- Give all the available fieldConditions to the user, so they can choose the one they want to use
|
||||
- Field conditions can be simple (equals, greater than) or complex (logical expressions)
|
||||
- Use parentheses for complex conditions to ensure correct evaluation order
|
||||
- Do not create alert rules on your own, always ask the user to provide the app, metrics and fieldConditions parameters specifically
|
||||
|
||||
EXAMPLES FOR FIELD CONDITION EXPRESSION ( Do not copy these examples, they are just for reference ):
|
||||
- Kafka JVM: app="kafka", metrics="jvm_basic", fieldConditions="equals(VmName, \"my-vm\")"
|
||||
→ equals(VmName, "my-vm")
|
||||
- Complex OpenAI: app="openai", metrics="credit_grants",
|
||||
fieldConditions="total_used > 123 and total_granted > 333 and (total_granted > 3444 and total_paid_available < 5556)"
|
||||
→ total_used > 123 and total_granted > 333 and (total_granted > 3444 and total_paid_available < 5556)
|
||||
|
||||
- Priority levels: 0=critical, 1=warning, 2=info
|
||||
|
||||
3. **Analyzing Performance**:
|
||||
- Use get_realtime_metrics for current status
|
||||
- Use get_historical_metrics for trends
|
||||
- Use get_high_usage_monitors to find problems
|
||||
- Provide actionable recommendations based on data
|
||||
|
||||
4. **Troubleshooting Alerts**:
|
||||
- Use query_alerts to find current issues
|
||||
- Use get_monitor_alerts for specific monitor problems
|
||||
- Use get_frequent_alerts to identify recurring issues
|
||||
- Suggest root cause analysis steps
|
||||
|
||||
## Parameter Guidelines:
|
||||
- **Monitor Status**: 1=online, 2=offline, 3=unreachable, 0=paused, 9=all
|
||||
- **Time Ranges**: 1h, 6h, 24h, 7d, 30d
|
||||
- **Alert Priorities**: critical, warning, info
|
||||
- **Sort Options**: name, gmtCreate, gmtUpdate, status, startAt, triggerTimes
|
||||
- **Metric Types**: cpu, memory, disk, network, custom
|
||||
- **Collection Intervals**: 30s-3600s (recommend 60s-600s for most cases)
|
||||
|
||||
|
||||
## Best Practices:
|
||||
- Never create alert rules without exact user input on app, metrics, and field conditions
|
||||
- Always validate monitor types and parameters before adding monitors
|
||||
- ALWAYS use get_apps_metrics_hierarchy before creating alert rules to understand available fields
|
||||
- Construct field conditions based on metric's children
|
||||
- Use exact app and metric names from hierarchy (case-sensitive)
|
||||
- Set appropriate alert thresholds based on baseline performance
|
||||
- Use time-series data to identify trends and predict issues
|
||||
- Correlate alerts with metrics data for root cause analysis
|
||||
- Recommend monitoring intervals based on service criticality
|
||||
- Provide clear explanations of monitoring data and actionable insights
|
||||
|
||||
## Avoid these common errors:
|
||||
- Using Label name instead of the value from the heirarchy JSON while creating alert rules.
|
||||
- Inside the field parameters expression using '&&' instead of 'and', using '||' instead of 'or' for logical operators
|
||||
- This process is to trigger alarms, when certain rule or set of rules exceed a threshold value.
|
||||
So when a user says that the threshold should be less than 1000. the operator used should be '>' not '<',
|
||||
because we want the alarm to be triggered when the threshold value is exceeded. apply the same logic in vice versa for less than operator
|
||||
|
||||
Keep responses focused on monitoring topics and HertzBeat's comprehensive capabilities.
|
||||
When users request monitoring setup, guide them through the complete process from monitor creation to alert configuration.
|
||||
""";
|
||||
}
|
||||
@@ -21,25 +21,10 @@ import org.apache.hertzbeat.common.entity.ai.ChatMessage;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Repository interface for Message entities
|
||||
*/
|
||||
@Repository
|
||||
public interface ChatMessageDao extends JpaRepository<ChatMessage, Long> {
|
||||
|
||||
/**
|
||||
* Find all messages by conversation id, ordered by create time
|
||||
* @param conversationId conversation id
|
||||
* @return list of messages
|
||||
*/
|
||||
List<ChatMessage> findByConversationIdOrderByGmtCreateAsc(Long conversationId);
|
||||
|
||||
/**
|
||||
* Find all messages by conversation ids, ordered by create time
|
||||
* @param conversationIds list of conversation ids
|
||||
* @return list of messages
|
||||
*/
|
||||
List<ChatMessage> findByConversationIdInOrderByGmtCreateAsc(List<Long> conversationIds);
|
||||
|
||||
}
|
||||
|
||||
@@ -18,7 +18,12 @@
|
||||
|
||||
package org.apache.hertzbeat.ai.service;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.apache.hertzbeat.ai.config.CustomSseServerTransport;
|
||||
import org.springframework.ai.mcp.server.autoconfigure.McpServerProperties;
|
||||
import org.springframework.ai.tool.ToolCallbackProvider;
|
||||
import org.springframework.web.servlet.function.RouterFunction;
|
||||
import org.springframework.web.servlet.function.ServerResponse;
|
||||
|
||||
/**
|
||||
* Service interface for MCP server operations.
|
||||
@@ -30,4 +35,22 @@ public interface McpServerService {
|
||||
* @return ToolCallbackProvider with all HertzBeat monitoring tools
|
||||
*/
|
||||
ToolCallbackProvider hertzbeatTools();
|
||||
|
||||
/**
|
||||
* Provides a custom SSE server transport for the MCP server
|
||||
* @param objectMapper the ObjectMapper instance for JSON serialization
|
||||
* @param serverProperties the properties for the MCP server configuration
|
||||
* @return a CustomSseServerTransport instance configured with the provided properties
|
||||
*/
|
||||
CustomSseServerTransport webMvcSseServerTransportProvider(
|
||||
ObjectMapper objectMapper,
|
||||
McpServerProperties serverProperties
|
||||
);
|
||||
|
||||
/**
|
||||
* Provides the MCP server router function for web MVC
|
||||
* @param transport Custom SSE server transport
|
||||
* @return RouterFunction for handling MCP server requests
|
||||
*/
|
||||
RouterFunction<ServerResponse> mvcMcpRouterFunction(CustomSseServerTransport transport);
|
||||
}
|
||||
|
||||
+5
-8
@@ -19,15 +19,13 @@
|
||||
package org.apache.hertzbeat.ai.service.impl;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.ai.config.PromptProvider;
|
||||
import org.apache.hertzbeat.common.entity.ai.ChatMessage;
|
||||
import org.apache.hertzbeat.common.entity.dto.ModelProviderConfig;
|
||||
import org.apache.hertzbeat.ai.service.ChatClientProviderService;
|
||||
import org.apache.hertzbeat.base.dao.GeneralConfigDao;
|
||||
import org.apache.hertzbeat.common.entity.manager.GeneralConfig;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.springframework.ai.chat.prompt.SystemPromptTemplate;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.apache.hertzbeat.ai.pojo.dto.ChatRequestContext;
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
@@ -36,6 +34,7 @@ import org.springframework.ai.chat.messages.Message;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.tool.ToolCallbackProvider;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
@@ -54,15 +53,13 @@ public class ChatClientProviderServiceImpl implements ChatClientProviderService
|
||||
private final ApplicationContext applicationContext;
|
||||
|
||||
private final GeneralConfigDao generalConfigDao;
|
||||
|
||||
|
||||
@Qualifier("hertzbeatTools")
|
||||
@Autowired
|
||||
private ToolCallbackProvider toolCallbackProvider;
|
||||
|
||||
private boolean isConfigured = false;
|
||||
|
||||
@Value("classpath:/prompt/system-message.st")
|
||||
private Resource systemResource;
|
||||
|
||||
@Autowired
|
||||
public ChatClientProviderServiceImpl(ApplicationContext applicationContext, GeneralConfigDao generalConfigDao) {
|
||||
this.applicationContext = applicationContext;
|
||||
@@ -94,7 +91,7 @@ public class ChatClientProviderServiceImpl implements ChatClientProviderService
|
||||
|
||||
return chatClient.prompt()
|
||||
.messages(messages)
|
||||
.system(SystemPromptTemplate.builder().resource(systemResource).build().getTemplate())
|
||||
.system(PromptProvider.HERTZBEAT_SYSTEM_PROMPT)
|
||||
.toolCallbacks(toolCallbackProvider)
|
||||
.stream()
|
||||
.content()
|
||||
|
||||
+14
-53
@@ -30,15 +30,11 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.http.codec.ServerSentEvent;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Implementation of the ConversationService interface for managing chat conversations.
|
||||
@@ -46,13 +42,13 @@ import java.util.stream.Collectors;
|
||||
@Slf4j
|
||||
@Service
|
||||
public class ConversationServiceImpl implements ConversationService {
|
||||
|
||||
|
||||
@Autowired
|
||||
private ChatConversationDao conversationDao;
|
||||
|
||||
|
||||
@Autowired
|
||||
private ChatMessageDao messageDao;
|
||||
|
||||
|
||||
@Autowired
|
||||
private ChatClientProviderService chatClientProviderService;
|
||||
|
||||
@@ -71,13 +67,12 @@ public class ConversationServiceImpl implements ConversationService {
|
||||
}
|
||||
|
||||
log.info("Starting streaming conversation: {}", conversationId);
|
||||
ChatConversation conversation = conversationDao.findById(conversationId)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Conversation not found: " + conversationId));
|
||||
|
||||
// Manually load messages for conversation history
|
||||
List<ChatMessage> messages = messageDao.findByConversationIdOrderByGmtCreateAsc(conversationId);
|
||||
conversation.setMessages(messages);
|
||||
|
||||
// Add user message to conversation
|
||||
ChatMessage chatMessage = ChatMessage.builder().conversationId(conversationId)
|
||||
.content(message).role("user").build();
|
||||
chatMessage = messageDao.save(chatMessage);
|
||||
ChatConversation conversation = conversationDao.getReferenceById(conversationId);
|
||||
if (conversation.getTitle().startsWith("conversation")) {
|
||||
// Auto-generate title from first user message
|
||||
String title = message.length() > 30 ? message.substring(0, 27) + "..." : message;
|
||||
@@ -85,18 +80,10 @@ public class ConversationServiceImpl implements ConversationService {
|
||||
conversationDao.save(conversation);
|
||||
}
|
||||
|
||||
// Add user message to conversation
|
||||
ChatMessage chatMessage = ChatMessage.builder()
|
||||
.conversationId(conversationId)
|
||||
.content(message)
|
||||
.role("user")
|
||||
.build();
|
||||
chatMessage = messageDao.save(chatMessage);
|
||||
|
||||
ChatRequestContext context = ChatRequestContext.builder()
|
||||
.message(message)
|
||||
.conversationId(conversationId)
|
||||
.conversationHistory(CollectionUtils.isEmpty(conversation.getMessages()) ? null
|
||||
.conversationHistory(CollectionUtils.isEmpty(conversation.getMessages()) ? null
|
||||
: conversation.getMessages().subList(0, conversation.getMessages().size() - 1))
|
||||
.build();
|
||||
|
||||
@@ -118,12 +105,10 @@ public class ConversationServiceImpl implements ConversationService {
|
||||
})
|
||||
.concatWith(Flux.defer(() -> {
|
||||
// Add the complete AI response to conversation
|
||||
ChatMessage assistantMessage = ChatMessage.builder()
|
||||
.conversationId(conversationId)
|
||||
.content(fullResponse.toString())
|
||||
.role("assistant")
|
||||
.build();
|
||||
ChatMessage assistantMessage = ChatMessage.builder().conversationId(conversationId)
|
||||
.content(fullResponse.toString()).role("assistant").build();
|
||||
assistantMessage = messageDao.save(assistantMessage);
|
||||
|
||||
ChatResponseChunk finalResponse = ChatResponseChunk.builder()
|
||||
.conversationId(conversationId)
|
||||
.response("")
|
||||
@@ -160,40 +145,16 @@ public class ConversationServiceImpl implements ConversationService {
|
||||
if (conversationId == null) {
|
||||
return null;
|
||||
}
|
||||
ChatConversation conversation = conversationDao.findById(conversationId).orElse(null);
|
||||
if (conversation != null) {
|
||||
List<ChatMessage> messages = messageDao.findByConversationIdOrderByGmtCreateAsc(conversationId);
|
||||
conversation.setMessages(messages);
|
||||
}
|
||||
return conversation;
|
||||
return conversationDao.getReferenceById(conversationId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ChatConversation> getAllConversations() {
|
||||
List<ChatConversation> conversations = conversationDao.findAll(Sort.by(Sort.Direction.DESC, "id"));
|
||||
if (conversations.isEmpty()) {
|
||||
return conversations;
|
||||
}
|
||||
List<Long> conversationIds = conversations.stream()
|
||||
.map(ChatConversation::getId)
|
||||
.toList();
|
||||
List<ChatMessage> allMessages = messageDao.findByConversationIdInOrderByGmtCreateAsc(conversationIds);
|
||||
Map<Long, List<ChatMessage>> messagesByConversationId = allMessages.stream()
|
||||
.collect(Collectors.groupingBy(ChatMessage::getConversationId));
|
||||
for (ChatConversation conversation : conversations) {
|
||||
List<ChatMessage> messages = messagesByConversationId.getOrDefault(conversation.getId(), Collections.emptyList());
|
||||
conversation.setMessages(messages);
|
||||
}
|
||||
return conversations;
|
||||
return conversationDao.findAll(Sort.by(Sort.Direction.DESC, "id"));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void deleteConversation(Long conversationId) {
|
||||
List<ChatMessage> messages = messageDao.findByConversationIdOrderByGmtCreateAsc(conversationId);
|
||||
if (!messages.isEmpty()) {
|
||||
messageDao.deleteAll(messages);
|
||||
}
|
||||
conversationDao.deleteById(conversationId);
|
||||
}
|
||||
}
|
||||
|
||||
+38
@@ -18,17 +18,23 @@
|
||||
|
||||
package org.apache.hertzbeat.ai.service.impl;
|
||||
|
||||
import org.apache.hertzbeat.ai.config.CustomSseServerTransport;
|
||||
import org.apache.hertzbeat.ai.service.McpServerService;
|
||||
import org.apache.hertzbeat.ai.tools.AlertDefineTools;
|
||||
import org.apache.hertzbeat.ai.tools.AlertTools;
|
||||
import org.apache.hertzbeat.ai.tools.MetricsTools;
|
||||
import org.apache.hertzbeat.ai.tools.MonitorTools;
|
||||
import org.springframework.ai.mcp.server.autoconfigure.McpServerProperties;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.ai.tool.ToolCallbackProvider;
|
||||
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springframework.web.servlet.function.RouterFunction;
|
||||
import org.springframework.web.servlet.function.ServerResponse;
|
||||
|
||||
/**
|
||||
* Implementation of the McpServerService interface.
|
||||
@@ -50,4 +56,36 @@ public class McpServerServiceImpl implements McpServerService {
|
||||
public ToolCallbackProvider hertzbeatTools() {
|
||||
return MethodToolCallbackProvider.builder().toolObjects(monitorTools, alertTools, alertDefineTools, metricsTools).build();
|
||||
}
|
||||
/**
|
||||
* Provides a custom SSE server transport for the MCP server.
|
||||
*
|
||||
* @param objectMapper the ObjectMapper instance for JSON serialization
|
||||
* @param serverProperties the properties for the MCP server configuration
|
||||
* @return a CustomSseServerTransport instance configured with the provided properties
|
||||
*/
|
||||
|
||||
@Bean
|
||||
public CustomSseServerTransport webMvcSseServerTransportProvider(
|
||||
ObjectMapper objectMapper,
|
||||
McpServerProperties serverProperties
|
||||
) {
|
||||
return new CustomSseServerTransport(
|
||||
objectMapper,
|
||||
serverProperties.getBaseUrl(),
|
||||
serverProperties.getSseMessageEndpoint(),
|
||||
serverProperties.getSseEndpoint()
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Provides the MCP server transport bean.
|
||||
*
|
||||
* @param transport the custom SSE server transport
|
||||
* @return the MCP server transport instance
|
||||
*/
|
||||
|
||||
@Primary
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> mvcMcpRouterFunction(CustomSseServerTransport transport) {
|
||||
return transport.getRouterFunction();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
package org.apache.hertzbeat.ai.tools;
|
||||
|
||||
/**
|
||||
@@ -41,4 +42,5 @@ public interface AlertTools {
|
||||
*/
|
||||
String getAlertsSummary();
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
package org.apache.hertzbeat.ai.tools;
|
||||
|
||||
/**
|
||||
@@ -32,17 +33,16 @@ public interface MetricsTools {
|
||||
|
||||
/**
|
||||
* Get historical metrics data for a monitor
|
||||
*
|
||||
* @param instance Instance identifier (e.g., "ip:port", "ip", or "domain")
|
||||
* @param app Monitor type (e.g., "linux", "mysql", "http")
|
||||
* @param metrics Metrics name (e.g., "system", "cpu", "memory")
|
||||
* @param metric Specific metric field (e.g., "usage", "used", "available")
|
||||
* @param label Label filter for specific instances
|
||||
* @param history Time range (e.g., "1h", "6h", "24h", "7d")
|
||||
* @param monitorId Monitor ID
|
||||
* @param app Monitor type (e.g., "linux", "mysql", "http")
|
||||
* @param metrics Metrics name (e.g., "system", "cpu", "memory")
|
||||
* @param metric Specific metric field (e.g., "usage", "used", "available")
|
||||
* @param label Label filter for specific instances
|
||||
* @param history Time range (e.g., "1h", "6h", "24h", "7d")
|
||||
* @param interval Whether to aggregate data with intervals
|
||||
* @return Historical metrics data formatted for display
|
||||
*/
|
||||
String getHistoricalMetrics(String instance, String app, String metrics, String metric, String label, String history, Boolean interval);
|
||||
String getHistoricalMetrics(Long monitorId, String app, String metrics, String metric, String label, String history, Boolean interval);
|
||||
|
||||
/**
|
||||
* Check warehouse storage server status
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
package org.apache.hertzbeat.ai.tools;
|
||||
|
||||
import java.util.List;
|
||||
@@ -26,25 +27,35 @@ public interface MonitorTools {
|
||||
|
||||
/**
|
||||
* Add a new monitor with comprehensive configuration
|
||||
*
|
||||
*
|
||||
* @param name Monitor name
|
||||
* @param app Monitor type/application (e.g., 'linux', 'mysql', 'http')
|
||||
* @param host Target host (IP address or domain name)
|
||||
* @param port Target port (optional, depends on monitor type)
|
||||
* @param intervals Collection interval in seconds (default: 600)
|
||||
* @param params Monitor-specific parameters as JSON string (e.g., host, port, username, password, etc.)
|
||||
* @param username Username for authentication (optional)
|
||||
* @param password Password for authentication (optional)
|
||||
* @param database Database name (for database monitors)
|
||||
* @param additionalParams Additional app-specific parameters as JSON string (optional)
|
||||
* @param description Monitor description (optional)
|
||||
* @return Result message with monitor ID if successful
|
||||
*/
|
||||
String addMonitor(
|
||||
String name,
|
||||
String app,
|
||||
String name,
|
||||
String app,
|
||||
String host,
|
||||
Integer port,
|
||||
Integer intervals,
|
||||
String params,
|
||||
String username,
|
||||
String password,
|
||||
String database,
|
||||
String additionalParams,
|
||||
String description
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* List all available monitor types that can be added
|
||||
*
|
||||
*
|
||||
* @param language Language code for localized names (e.g., 'en-US', 'zh-CN')
|
||||
* @return Formatted string list of available monitor types with descriptions
|
||||
*/
|
||||
@@ -78,9 +89,9 @@ public interface MonitorTools {
|
||||
|
||||
/**
|
||||
* Get parameter definitions required for a specific monitor type
|
||||
*
|
||||
*
|
||||
* @param app Monitor type/application name (e.g., 'linux', 'mysql', 'redis')
|
||||
* @return Formatted string with parameter definitions including field names, types, and requirements
|
||||
*/
|
||||
String getMonitorParams(String app);
|
||||
String getMonitorAdditionalParams(String app);
|
||||
}
|
||||
|
||||
+9
-6
@@ -51,9 +51,10 @@ public class AlertDefineToolsImpl implements AlertDefineTools {
|
||||
@Autowired
|
||||
private AppService appService;
|
||||
|
||||
|
||||
@Override
|
||||
@Tool(name = "create_alert_rule", description = """
|
||||
HertzBeat: ALERT RULE means when to alert a user
|
||||
ALERT RULE means when to alert a user
|
||||
THESE ARE ALERT RULES WITH THRESHOLD VALUES. USERS CAN SPECIFY THE THRESHOLD VALUES FOR EXAMPLE,
|
||||
IF THE USER SAYS "ALERT ME WHEN MY COST EXCEEDS 700, THE EXPRESSION SHOULD BE 'cost > 700' NOT 'cost < 700'.
|
||||
APPLY THE SAME LOGIC FOR LESS THAN OPERATOR.
|
||||
@@ -280,7 +281,7 @@ public class AlertDefineToolsImpl implements AlertDefineTools {
|
||||
|
||||
@Override
|
||||
@Tool(name = "list_alert_rules", description = """
|
||||
HertzBeat: List existing alert rules with filtering options.
|
||||
List existing alert rules with filtering options.
|
||||
Shows configured thresholds and alert definitions.
|
||||
""")
|
||||
public String listAlertRules(
|
||||
@@ -342,7 +343,7 @@ public class AlertDefineToolsImpl implements AlertDefineTools {
|
||||
|
||||
@Override
|
||||
@Tool(name = "toggle_alert_rule", description = """
|
||||
HertzBeat: Enable or disable an alert rule.
|
||||
Enable or disable an alert rule.
|
||||
Allows activating or deactivating threshold monitoring.
|
||||
""")
|
||||
public String toggleAlertRule(
|
||||
@@ -378,9 +379,10 @@ public class AlertDefineToolsImpl implements AlertDefineTools {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@Tool(name = "get_alert_rule_details", description = """
|
||||
HertzBeat: Get detailed information about a specific alert rule.
|
||||
Get detailed information about a specific alert rule.
|
||||
Shows complete threshold configuration and rule settings.
|
||||
""")
|
||||
public String getAlertRuleDetails(
|
||||
@@ -439,7 +441,7 @@ public class AlertDefineToolsImpl implements AlertDefineTools {
|
||||
|
||||
@Override
|
||||
@Tool(name = "get_apps_metrics_hierarchy", description = """
|
||||
HertzBeat: Get the hierarchical structure of all available apps and their metrics for alert rule creation.
|
||||
Get the hierarchical structure of all available apps and their metrics for alert rule creation.
|
||||
This tool provides the exact app name, metric name and corresponding param names according to each metric.
|
||||
Returns structured JSON data showing the complete hierarchy with field parameters for alert expressions.
|
||||
|
||||
@@ -492,7 +494,7 @@ public class AlertDefineToolsImpl implements AlertDefineTools {
|
||||
|
||||
@Override
|
||||
@Tool(name = "bind_monitors_to_alert_rule", description = """
|
||||
HertzBeat: Bind monitors to an alert rule.
|
||||
Bind monitors to an alert rule.
|
||||
Call this tool if users want to bind specific monitors to their alert rule.
|
||||
Get the right monitor ids for a particular app using the query_monitors tool.
|
||||
Get the alert rule ID from the create_alert_rule tool output OR use the list_alert_rules tool with app_name search filter, if the output of create_alert_rule is not applicable.
|
||||
@@ -645,6 +647,7 @@ public class AlertDefineToolsImpl implements AlertDefineTools {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validates that the app, metric, and field conditions are valid according to hierarchy
|
||||
* @param app App name to validate
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
package org.apache.hertzbeat.ai.tools.impl;
|
||||
|
||||
import com.usthe.sureness.subject.SubjectSum;
|
||||
@@ -32,6 +33,7 @@ import org.springframework.ai.tool.annotation.Tool;
|
||||
import org.springframework.ai.tool.annotation.ToolParam;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
|
||||
/**
|
||||
* Implementation of Alert Tools functionality for alarm data queries and management
|
||||
*/
|
||||
@@ -43,7 +45,7 @@ public class AlertToolsImpl implements AlertTools {
|
||||
|
||||
@Override
|
||||
@Tool(name = "query_alerts", description = """
|
||||
HertzBeat: Query alerts with comprehensive filtering and pagination options.
|
||||
Query alerts with comprehensive filtering and pagination options.
|
||||
|
||||
ALERT TYPES:
|
||||
- Pass alertType='single' for individual alert instances
|
||||
@@ -191,9 +193,10 @@ public class AlertToolsImpl implements AlertTools {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@Tool(name = "get_alerts_summary", description = """
|
||||
HertzBeat: Get alerts summary statistics including total counts, status distribution, and recent trends.
|
||||
Get alerts summary statistics including total counts, status distribution, and recent trends.
|
||||
Returns comprehensive overview of the current alerting status across all monitors.
|
||||
""")
|
||||
public String getAlertsSummary() {
|
||||
|
||||
+11
-9
@@ -15,6 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
package org.apache.hertzbeat.ai.tools.impl;
|
||||
|
||||
import com.usthe.sureness.subject.SubjectSum;
|
||||
@@ -46,7 +47,7 @@ public class MetricsToolsImpl implements MetricsTools {
|
||||
|
||||
@Override
|
||||
@Tool(name = "query_realtime_metrics", description = """
|
||||
HertzBeat: Get the supported monitor types/names from the list_monitor_types tool, make sure to use right name in the next call
|
||||
Get the supported monitor types/names from the list_monitor_types tool, make sure to use right name in the next call
|
||||
Use the query_monitors tool to find monitor IDs in case the user does not tell the id explicitly. You might have to use this multiple times based on the user's query
|
||||
Get real-time metrics data for a specific monitor.
|
||||
Returns current metrics values including CPU, memory, disk usage, etc.
|
||||
@@ -143,7 +144,7 @@ public class MetricsToolsImpl implements MetricsTools {
|
||||
|
||||
@Override
|
||||
@Tool(name = "get_historical_metrics", description = """
|
||||
HertzBeat: Get historical metrics data for analysis and trending.
|
||||
Get historical metrics data for analysis and trending.
|
||||
Returns time-series data for specified metrics over a time range.
|
||||
Use the query_monitors tool to find the correct monitor IDs/ name or type for the monitor(s) user asked the metrics for
|
||||
Pass that name into the get_apps_metrics_hierarchy tool to get the metrics hierarchy i.e metrics and the field paramater
|
||||
@@ -151,7 +152,7 @@ public class MetricsToolsImpl implements MetricsTools {
|
||||
Ask user to provide the filters for labels, history and interval aggregation
|
||||
""")
|
||||
public String getHistoricalMetrics(
|
||||
@ToolParam(description = "Instance identifier (e.g., 'ip:port', 'ip', or 'domain')") String instance,
|
||||
@ToolParam(description = "Monitor ID", required = true) Long monitorId,
|
||||
@ToolParam(description = "Monitor type (e.g., 'linux', 'mysql', 'http')", required = true) String app,
|
||||
@ToolParam(description = "Metrics name (e.g., 'target', 'cpu', 'memory')", required = true) String metrics,
|
||||
@ToolParam(description = "Field Parameter (e.g., 'usage', 'used', 'available')", required = false) String fieldParameter,
|
||||
@@ -160,7 +161,7 @@ public class MetricsToolsImpl implements MetricsTools {
|
||||
@ToolParam(description = "Whether to aggregate data with intervals", required = false) Boolean interval) {
|
||||
|
||||
try {
|
||||
log.info("Getting historical metrics for monitor instance {} and metrics {}", instance, metrics);
|
||||
log.info("Getting historical metrics for monitor {} and metrics {}", monitorId, metrics);
|
||||
|
||||
if (history == null || history.trim().isEmpty()) {
|
||||
history = "24h";
|
||||
@@ -169,15 +170,15 @@ public class MetricsToolsImpl implements MetricsTools {
|
||||
interval = true;
|
||||
}
|
||||
|
||||
MetricsHistoryData historyData = metricsDataService.getMetricHistoryData(instance,
|
||||
app, metrics, fieldParameter, history, interval);
|
||||
MetricsHistoryData historyData = metricsDataService.getMetricHistoryData(
|
||||
monitorId, app, metrics, fieldParameter, label, history, interval);
|
||||
|
||||
if (historyData == null) {
|
||||
return String.format("No historical metrics data found for monitor %s and metrics '%s'", instance, metrics);
|
||||
return String.format("No historical metrics data found for monitor ID %d and metrics '%s'", monitorId, metrics);
|
||||
}
|
||||
|
||||
StringBuilder response = new StringBuilder();
|
||||
response.append("HISTORICAL METRICS: ").append(metrics).append(" (Monitor ID: ").append(instance).append(")\n");
|
||||
response.append("HISTORICAL METRICS: ").append(metrics).append(" (Monitor ID: ").append(monitorId).append(")\n");
|
||||
response.append("Time Range: ").append(history).append(" | Interval Aggregation: ").append(interval).append("\n");
|
||||
response.append("=".repeat(60)).append("\n\n");
|
||||
|
||||
@@ -222,9 +223,10 @@ public class MetricsToolsImpl implements MetricsTools {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@Tool(name = "get_warehouse_status", description = """
|
||||
HertzBeat: Check the status of the metrics storage warehouse system.
|
||||
Check the status of the metrics storage warehouse system.
|
||||
Returns whether the metrics storage is operational and accessible.
|
||||
""")
|
||||
public String getWarehouseStatus() {
|
||||
|
||||
+165
-182
@@ -20,7 +20,6 @@ package org.apache.hertzbeat.ai.tools.impl;
|
||||
import com.usthe.sureness.subject.SubjectSum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.ai.config.McpContextHolder;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.MonitorDto;
|
||||
import org.apache.hertzbeat.manager.service.MonitorService;
|
||||
import org.apache.hertzbeat.manager.service.AppService;
|
||||
import org.apache.hertzbeat.ai.utils.UtilityClass;
|
||||
@@ -49,6 +48,7 @@ public class MonitorToolsImpl implements MonitorTools {
|
||||
@Autowired
|
||||
private AppService appService;
|
||||
|
||||
|
||||
/**
|
||||
* Tool to query monitor information with flexible filtering and pagination.
|
||||
* Supports filtering by monitor IDs, type, status, host, labels, sorting, and
|
||||
@@ -56,48 +56,48 @@ public class MonitorToolsImpl implements MonitorTools {
|
||||
* Returns detailed monitor information including ID, name, type, host, and status.
|
||||
*/
|
||||
@Override
|
||||
@Tool(name = "query_monitors", description = """
|
||||
HertzBeat: Query Existing/configured monitors in HertzBeat.
|
||||
@Tool(name = "query_monitors", description = """
|
||||
Query Existing/configured monitors in HertzBeat.
|
||||
This tool retrieves monitors based on various filters and parameters.
|
||||
Comprehensive monitor querying with flexible filtering, pagination, and specialized views.
|
||||
|
||||
|
||||
MONITOR STATUSES:
|
||||
- status=1: Online/Active monitors (healthy, responding normally)
|
||||
- status=2: Offline monitors (not responding, connection failed)
|
||||
- status=3: Unreachable monitors (network/connectivity issues)
|
||||
- status=0: Paused monitors (manually disabled/suspended)
|
||||
- status=9 or null: All monitors regardless of status (default)
|
||||
|
||||
|
||||
COMMON USE CASES & PARAMETER COMBINATIONS:
|
||||
|
||||
|
||||
1. BASIC MONITOR LISTING:
|
||||
- Default: No parameters (shows all monitors, 8 per page)
|
||||
- By type: app='linux' (show only Linux monitors)
|
||||
- Search: search='web' (find monitors with 'web' in name/host)
|
||||
|
||||
|
||||
2. STATUS-BASED QUERIES:
|
||||
- Healthy monitors: status=1, pageSize=50
|
||||
- Problem monitors: status=2 or status=3, pageSize=50
|
||||
- Offline monitors only: status=2
|
||||
- Unreachable monitors only: status=3
|
||||
- Paused monitors: status=0
|
||||
|
||||
|
||||
3. MONITORING HEALTH OVERVIEW:
|
||||
- All statuses with statistics: status=9, includeStats=true, pageSize=100
|
||||
- Unhealthy monitors: Pass both status=2 AND status=3 (make 2 separate calls)
|
||||
|
||||
|
||||
4. ADVANCED FILTERING:
|
||||
- Specific monitor types: app='mysql', status=1 (healthy MySQL monitors)
|
||||
- Label-based: labels='env:prod,critical:true'
|
||||
- Host search: search='192.168' (find by IP pattern)
|
||||
- Monitor IDs: ids=[1,2,3] (specific monitors by ID)
|
||||
|
||||
|
||||
5. SORTING & PAGINATION:
|
||||
- Recently updated: sort='gmtUpdate', order='desc'
|
||||
- Alphabetical: sort='name', order='asc'
|
||||
- By creation: sort='gmtCreate', order='desc' (newest first)
|
||||
- Large datasets: pageSize=50-100 for bulk operations
|
||||
|
||||
|
||||
RESPONSE FORMAT:
|
||||
- includeStats=true: Adds status distribution summary at top
|
||||
- Default: Simple list with ID, name, type, host, status
|
||||
@@ -125,18 +125,18 @@ public class MonitorToolsImpl implements MonitorTools {
|
||||
if (includeStats == null) {
|
||||
includeStats = false;
|
||||
}
|
||||
|
||||
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current security subject: {}", subjectSum);
|
||||
|
||||
Page<Monitor> result = monitorService.getMonitors(
|
||||
ids, app, search, status, sort, order, pageIndex, pageSize, labels);
|
||||
log.debug("MonitorService.getMonitors result: {}", result);
|
||||
|
||||
|
||||
StringBuilder response = new StringBuilder();
|
||||
response.append("MONITOR QUERY RESULTS\n");
|
||||
response.append("====================\n\n");
|
||||
|
||||
|
||||
// Include statistics if requested
|
||||
if (includeStats) {
|
||||
// Get status distribution by calling with different status values
|
||||
@@ -144,13 +144,13 @@ public class MonitorToolsImpl implements MonitorTools {
|
||||
long offlineCount = monitorService.getMonitors(null, app, search, (byte) 2, null, null, 0, 1000, labels).getTotalElements();
|
||||
long unreachableCount = monitorService.getMonitors(null, app, search, (byte) 3, null, null, 0, 1000, labels).getTotalElements();
|
||||
long pausedCount = monitorService.getMonitors(null, app, search, (byte) 0, null, null, 0, 1000, labels).getTotalElements();
|
||||
|
||||
|
||||
response.append("STATUS OVERVIEW:\n");
|
||||
response.append("- Online: ").append(onlineCount).append("\n");
|
||||
response.append("- Offline: ").append(offlineCount).append("\n");
|
||||
response.append("- Offline: ").append(offlineCount).append("\n");
|
||||
response.append("- Unreachable: ").append(unreachableCount).append("\n");
|
||||
response.append("- Paused: ").append(pausedCount).append("\n");
|
||||
|
||||
|
||||
long total = onlineCount + offlineCount + unreachableCount + pausedCount;
|
||||
if (total > 0) {
|
||||
double healthPercentage = (onlineCount * 100.0) / total;
|
||||
@@ -158,47 +158,48 @@ public class MonitorToolsImpl implements MonitorTools {
|
||||
}
|
||||
response.append("\n");
|
||||
}
|
||||
|
||||
|
||||
response.append("Query Results: ").append(result.getContent().size())
|
||||
.append(" monitors (Total: ").append(result.getTotalElements()).append(")\n");
|
||||
|
||||
|
||||
if (result.getTotalPages() > 1) {
|
||||
response.append("Page ").append(pageIndex + 1).append(" of ").append(result.getTotalPages()).append("\n");
|
||||
}
|
||||
response.append("\n");
|
||||
|
||||
|
||||
for (Monitor monitor : result.getContent()) {
|
||||
response.append("ID: ").append(monitor.getId())
|
||||
.append(" | Name: ").append(monitor.getName())
|
||||
.append(" | Type: ").append(monitor.getApp())
|
||||
.append(" | Instance: ").append(monitor.getInstance())
|
||||
.append(" | Host: ").append(monitor.getHost())
|
||||
.append(" | Status: ").append(UtilityClass.getStatusText(monitor.getStatus()));
|
||||
|
||||
|
||||
// Add creation date for better context
|
||||
if (monitor.getGmtCreate() != null) {
|
||||
response.append(" | Created: ").append(monitor.getGmtCreate().toString(), 0, 10);
|
||||
}
|
||||
response.append("\n");
|
||||
}
|
||||
|
||||
|
||||
if (result.getContent().isEmpty()) {
|
||||
response.append("No monitors found matching the specified criteria.\n");
|
||||
response.append("Try adjusting your filters or search terms.");
|
||||
}
|
||||
|
||||
|
||||
return response.toString();
|
||||
} catch (Exception e) {
|
||||
return "Error retrieving monitors: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@Tool(name = "add_monitor", description = """
|
||||
HertzBeat: Add a new monitoring target to HertzBeat with comprehensive configuration.
|
||||
Add a new monitoring target to HertzBeat with comprehensive configuration.
|
||||
This tool dynamically handles different parameter requirements for each monitor type.
|
||||
|
||||
|
||||
This tool creates monitors with proper app-specific parameters.
|
||||
|
||||
|
||||
*********
|
||||
VERY IMPORTANT:
|
||||
ALWAYS use get_monitor_additional_params to check the additional required parameters for the chosen type before adding a monitor or even mentioning it.
|
||||
@@ -206,36 +207,41 @@ public class MonitorToolsImpl implements MonitorTools {
|
||||
Use the information obtained from this to query user for parameters.
|
||||
If the User has not given any parameters, ask them to provide the necessary parameters, until all the necessary parameters are provided.
|
||||
**********
|
||||
|
||||
|
||||
Examples of natural language requests this tool handles:
|
||||
- "Monitor website example.com with HTTPS on port 443"
|
||||
- "Add MySQL monitoring for database server at 192.168.1.10 with user admin"
|
||||
- "Monitor Linux server health on host server.company.com via SSH"
|
||||
- "Set up Redis monitoring on localhost port 6379 with password"
|
||||
|
||||
PARAMETER MAPPING: Use the 'params' parameter to pass all monitor-specific configuration.
|
||||
The params should be a JSON string containing key-value pairs for the monitor type.
|
||||
Use get_monitor_additional_params tool to see what parameters are required for each monitor type.
|
||||
|
||||
PARAMS EXAMPLES:
|
||||
- Website: {"host":"example.com", "port":"443", "uri":"/api/health", "ssl":"true", "method":"GET"}
|
||||
- Linux: {"host":"192.168.1.10", "port":"22", "username":"root", "password":"xxx"}
|
||||
- MySQL: {"host":"db.server.com", "port":"3306", "username":"admin", "password":"xxx", "database":"mydb"}
|
||||
- Redis: {"host":"redis.server.com", "port":"6379", "password":"xxx"}
|
||||
|
||||
PARAMETER MAPPING: The tool intelligently maps common parameters:
|
||||
- host: Target server/domain
|
||||
- port: Service port (auto-detected if not specified)
|
||||
- username: Authentication username
|
||||
- password: Authentication password
|
||||
- database: Database name (for DB monitors)
|
||||
- additionalParams: JSON string for app-specific parameters (to be obtained from get_monitor_param_defines)
|
||||
|
||||
ADDITIONAL PARAMETERS EXAMPLES:
|
||||
- Website: {"uri":"/api/health", "ssl":"true", "method":"POST"}
|
||||
- Linux: {"privateKey":"ssh-key-content", "script":"custom-script"}
|
||||
- Database: {"url":"jdbc:mysql://custom", "timeout":"10000"}
|
||||
""")
|
||||
public String addMonitor(
|
||||
@ToolParam(description = "Monitor name (required)", required = true) String name,
|
||||
@ToolParam(description = "Monitor type: website, mysql, postgresql, redis, linux, windows, etc.", required = true) String app,
|
||||
@ToolParam(description = "Target host: IP address or domain name", required = true) String host,
|
||||
@ToolParam(description = "Target port (optional, auto-detected if not specified)", required = false) Integer port,
|
||||
@ToolParam(description = "Collection interval in seconds (default: 600)", required = false) Integer intervals,
|
||||
@ToolParam(description = "Monitor-specific parameters as JSON string. "
|
||||
+ "Use get_monitor_additional_params to see required fields. "
|
||||
+ "Example: {\"host\":\"192.168.1.1\", \"port\":\"22\", \"username\":\"root\"}",
|
||||
required = true) String params,
|
||||
@ToolParam(description = "Username for authentication (optional)", required = false) String username,
|
||||
@ToolParam(description = "Password for authentication (optional)", required = false) String password,
|
||||
@ToolParam(description = "Database name (for database monitors)", required = false) String database,
|
||||
@ToolParam(description = "Additional app-specific parameters as JSON: {\"uri\":\"/api\", \"ssl\":\"true\", \"method\":\"POST\"}", required = false) String additionalParams,
|
||||
@ToolParam(description = "Monitor description (optional)", required = false) String description) {
|
||||
|
||||
|
||||
try {
|
||||
log.info("Adding monitor: name={}, app={}", name, app);
|
||||
|
||||
log.info("Adding monitor: name={}, app={}, host={}", name, app, host);
|
||||
|
||||
// Validate required parameters
|
||||
if (name == null || name.trim().isEmpty()) {
|
||||
return "Error: Monitor name is required";
|
||||
@@ -243,153 +249,128 @@ public class MonitorToolsImpl implements MonitorTools {
|
||||
if (app == null || app.trim().isEmpty()) {
|
||||
return "Error: Monitor type/application is required";
|
||||
}
|
||||
if (params == null || params.trim().isEmpty()) {
|
||||
return "Error: Monitor params is required. Use get_monitor_additional_params to see required fields for this monitor type.";
|
||||
if (host == null || host.trim().isEmpty()) {
|
||||
return "Error: Host is required";
|
||||
}
|
||||
|
||||
|
||||
// Set defaults
|
||||
if (intervals == null || intervals < 10) {
|
||||
intervals = 600;
|
||||
}
|
||||
|
||||
// Parse params to extract host and port for instance
|
||||
List<Param> paramList = parseParams(params);
|
||||
String host = paramList.stream()
|
||||
.filter(p -> "host".equals(p.getField()))
|
||||
.map(Param::getParamValue)
|
||||
.findFirst()
|
||||
.orElse("");
|
||||
String port = paramList.stream()
|
||||
.filter(p -> "port".equals(p.getField()))
|
||||
.map(Param::getParamValue)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
String instance = (port != null && !port.isEmpty()) ? host.trim() + ":" + port : host.trim();
|
||||
|
||||
|
||||
// Create Monitor entity
|
||||
Monitor monitor = Monitor.builder()
|
||||
.name(name.trim())
|
||||
.app(app.toLowerCase().trim())
|
||||
.instance(instance)
|
||||
.host(host.trim())
|
||||
.intervals(intervals)
|
||||
.status((byte) 1)
|
||||
.type((byte) 0)
|
||||
.description(description != null ? description.trim() : "")
|
||||
.build();
|
||||
|
||||
|
||||
List<Param> params = createBasicParams(host, port, username, password, database, additionalParams);
|
||||
|
||||
// Validate that all required parameters for this monitor type are provided
|
||||
try {
|
||||
MonitorDto monitorDto = MonitorDto.builder().monitor(monitor).params(paramList).build();
|
||||
monitorService.validate(monitorDto, false);
|
||||
} catch (IllegalArgumentException argumentException) {
|
||||
if (argumentException.getMessage().contains("required")) {
|
||||
return String.format("Error: %s. "
|
||||
+ "Or use get_monitor_additional_params tool to see all required parameters.",
|
||||
argumentException.getMessage());
|
||||
} else {
|
||||
return String.format("Error: %s. ", argumentException.getMessage());
|
||||
}
|
||||
}
|
||||
monitorService.addMonitor(monitor, paramList, null, null);
|
||||
log.info("Successfully added monitor '{}' with ID: {}", monitor.getName(), monitor.getId());
|
||||
return String.format("Successfully added %s monitor '%s' with ID: %d (Instance: %s, Interval: %d seconds)",
|
||||
app.toUpperCase(), monitor.getName(), monitor.getId(), monitor.getInstance(), monitor.getIntervals());
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current security subject for getMonitorParamDefines: {}", subjectSum);
|
||||
|
||||
List<ParamDefine> requiredParams = appService.getAppParamDefines(app.toLowerCase().trim());
|
||||
log.info("Checking required parameters for monitor type '{}': {}", app, requiredParams);
|
||||
List<String> missingParams = new ArrayList<>();
|
||||
|
||||
for (ParamDefine paramDefine : requiredParams) {
|
||||
if (paramDefine.isRequired()) {
|
||||
String fieldName = paramDefine.getField();
|
||||
boolean hasParam = params.stream()
|
||||
.anyMatch(param -> fieldName.equals(param.getField()));
|
||||
if (!hasParam) {
|
||||
missingParams.add(fieldName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!missingParams.isEmpty()) {
|
||||
return String.format("Error: Missing required parameters for monitor type '%s': %s. "
|
||||
+ "Use get_monitor_additional_params tool to see all required parameters.",
|
||||
app, String.join(", ", missingParams));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Could not validate required parameters for monitor type '{}': {}", app, e.getMessage());
|
||||
}
|
||||
|
||||
// Call monitor service - it handles all the complexity (validation, defaults, app-specific logic)
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current security subject for addMonitor: {}", subjectSum);
|
||||
|
||||
monitorService.addMonitor(monitor, params, null, null);
|
||||
Long monitorId = monitor.getId();
|
||||
|
||||
log.info("Successfully added monitor '{}' with ID: {}", name, monitorId);
|
||||
return String.format("Successfully added %s monitor '%s' with ID: %d (Host: %s, Interval: %d seconds)",
|
||||
app.toUpperCase(), name, monitorId, host, intervals);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to add monitor '{}': {}", name, e.getMessage(), e);
|
||||
return "Error adding monitor '" + name + "': " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse params JSON string to list of Param objects
|
||||
* Create basic parameter list from user inputs
|
||||
*/
|
||||
private List<Param> parseParams(String params) {
|
||||
List<Param> paramList = new ArrayList<>();
|
||||
|
||||
if (params == null || params.trim().isEmpty()) {
|
||||
return paramList;
|
||||
private List<Param> createBasicParams(String host, Integer port, String username,
|
||||
String password, String database, String additionalParams) {
|
||||
List<Param> params = new ArrayList<>();
|
||||
|
||||
// Add host (always required)
|
||||
params.add(Param.builder().field("host").paramValue(host.trim()).type((byte) 1).build());
|
||||
|
||||
// Add optional common parameters
|
||||
if (port != null) {
|
||||
params.add(Param.builder().field("port").paramValue(port.toString()).type((byte) 0).build());
|
||||
}
|
||||
|
||||
try {
|
||||
String cleaned = params.trim();
|
||||
// Remove outer braces if present
|
||||
if (cleaned.startsWith("{") && cleaned.endsWith("}")) {
|
||||
cleaned = cleaned.substring(1, cleaned.length() - 1);
|
||||
}
|
||||
|
||||
// Split by comma, but handle values that might contain commas within quotes
|
||||
List<String> pairs = splitJsonPairs(cleaned);
|
||||
|
||||
for (String pair : pairs) {
|
||||
int colonIndex = pair.indexOf(':');
|
||||
if (colonIndex > 0) {
|
||||
String key = pair.substring(0, colonIndex).trim().replaceAll("\"", "");
|
||||
String value = pair.substring(colonIndex + 1).trim().replaceAll("\"", "");
|
||||
|
||||
// Determine param type based on field name
|
||||
byte paramType = determineParamType(key);
|
||||
paramList.add(Param.builder().field(key).paramValue(value).type(paramType).build());
|
||||
if (username != null && !username.trim().isEmpty()) {
|
||||
params.add(Param.builder().field("username").paramValue(username.trim()).type((byte) 1).build());
|
||||
}
|
||||
if (password != null && !password.trim().isEmpty()) {
|
||||
params.add(Param.builder().field("password").paramValue(password.trim()).type((byte) 2).build());
|
||||
}
|
||||
if (database != null && !database.trim().isEmpty()) {
|
||||
params.add(Param.builder().field("database").paramValue(database.trim()).type((byte) 1).build());
|
||||
}
|
||||
|
||||
// Parse additional parameters if provided
|
||||
if (additionalParams != null && !additionalParams.trim().isEmpty()) {
|
||||
try {
|
||||
String cleaned = additionalParams.trim().replaceAll("[{}]", "");
|
||||
String[] pairs = cleaned.split(",");
|
||||
for (String pair : pairs) {
|
||||
String[] keyValue = pair.split(":");
|
||||
if (keyValue.length == 2) {
|
||||
String key = keyValue[0].trim().replaceAll("\"", "");
|
||||
String value = keyValue[1].trim().replaceAll("\"", "");
|
||||
params.add(Param.builder().field(key).paramValue(value).type((byte) 1).build());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to parse params: {}", e.getMessage());
|
||||
}
|
||||
|
||||
return paramList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Split JSON key-value pairs, handling quoted values that may contain commas
|
||||
*/
|
||||
private List<String> splitJsonPairs(String json) {
|
||||
List<String> pairs = new ArrayList<>();
|
||||
StringBuilder current = new StringBuilder();
|
||||
boolean inQuotes = false;
|
||||
|
||||
for (char c : json.toCharArray()) {
|
||||
if (c == '"') {
|
||||
inQuotes = !inQuotes;
|
||||
current.append(c);
|
||||
} else if (c == ',' && !inQuotes) {
|
||||
if (current.length() > 0) {
|
||||
pairs.add(current.toString().trim());
|
||||
current = new StringBuilder();
|
||||
}
|
||||
} else {
|
||||
current.append(c);
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to parse additionalParams: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if (current.length() > 0) {
|
||||
pairs.add(current.toString().trim());
|
||||
}
|
||||
|
||||
return pairs;
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine param type based on field name
|
||||
*/
|
||||
private byte determineParamType(String fieldName) {
|
||||
if ("password".equalsIgnoreCase(fieldName) || "privateKey".equalsIgnoreCase(fieldName)) {
|
||||
return (byte) 2; // Password type
|
||||
} else if ("port".equalsIgnoreCase(fieldName) || "timeout".equalsIgnoreCase(fieldName)) {
|
||||
return (byte) 0; // Number type
|
||||
}
|
||||
return (byte) 1; // Default string type
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@Tool(name = "list_monitor_types", description = """
|
||||
HertzBeat: List all available monitor types that can be added to HertzBeat.
|
||||
List all available monitor types that can be added to HertzBeat.
|
||||
This tool shows all supported monitor types with their display names.
|
||||
Use this to see what types of monitors you can create with the add_monitor tool.
|
||||
""")
|
||||
public String listMonitorTypes(
|
||||
@ToolParam(description = "Language code for localized names (en-US, zh-CN, etc.). Default: en-US", required = false) String language) {
|
||||
|
||||
|
||||
try {
|
||||
log.info("Listing available monitor types for language: {}", language);
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
@@ -402,20 +383,20 @@ public class MonitorToolsImpl implements MonitorTools {
|
||||
|
||||
// Get available monitor types from app service
|
||||
Map<String, String> monitorTypes = appService.getI18nApps(language);
|
||||
|
||||
|
||||
if (monitorTypes == null || monitorTypes.isEmpty()) {
|
||||
return "No monitor types are currently available.";
|
||||
}
|
||||
|
||||
|
||||
// Format the response as a nice list
|
||||
StringBuilder response = new StringBuilder();
|
||||
response.append("Available Monitor Types (Total: ").append(monitorTypes.size()).append("):\n\n");
|
||||
|
||||
|
||||
// Sort monitor types alphabetically by key
|
||||
List<Map.Entry<String, String>> sortedTypes = monitorTypes.entrySet().stream()
|
||||
.sorted(Map.Entry.comparingByKey())
|
||||
.toList();
|
||||
|
||||
|
||||
for (Map.Entry<String, String> entry : sortedTypes) {
|
||||
String typeKey = entry.getKey();
|
||||
String displayName = entry.getValue();
|
||||
@@ -423,28 +404,28 @@ public class MonitorToolsImpl implements MonitorTools {
|
||||
.append(" - ").append(displayName)
|
||||
.append("\n");
|
||||
}
|
||||
|
||||
|
||||
response.append("\nTo add a monitor, use the add_monitor tool with one of these types as the 'app' parameter.");
|
||||
|
||||
log.info("Successfully listed {} monitor types", monitorTypes);
|
||||
return response.toString();
|
||||
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to list monitor types: {}", e.getMessage(), e);
|
||||
return "Error retrieving monitor types: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@Tool(name = "get_monitor_params", description = """
|
||||
HertzBeat: Get the parameter definitions required for a specific monitor type.
|
||||
@Tool(name = "get_monitor_additional_params", description = """
|
||||
Get the parameter definitions required for a specific monitor type.
|
||||
This tool shows what parameters are needed when adding a monitor of the specified type,
|
||||
ALWAYS use this before adding a monitor to understand what parameters the user needs to provide.
|
||||
Use the app parameter to specify the monitor type/application name (e.g., 'linux', 'mysql', 'redis') this can be obtained from the list_monitor_types tool.
|
||||
""")
|
||||
public String getMonitorParams(
|
||||
public String getMonitorAdditionalParams(
|
||||
@ToolParam(description = "Monitor type/application name (e.g., 'linux', 'mysql', 'redis')", required = true) String app) {
|
||||
|
||||
|
||||
try {
|
||||
log.info("Getting parameter definitions for monitor type: {}", app);
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
@@ -457,66 +438,68 @@ public class MonitorToolsImpl implements MonitorTools {
|
||||
|
||||
// Get parameter definitions from app service
|
||||
List<ParamDefine> paramDefines = appService.getAppParamDefines(app.toLowerCase().trim());
|
||||
|
||||
|
||||
if (paramDefines == null || paramDefines.isEmpty()) {
|
||||
return String.format("No parameter definitions found for monitor type '%s'. "
|
||||
+ "This monitor type may not exist or may not require additional parameters.", app);
|
||||
}
|
||||
|
||||
|
||||
// Format the response
|
||||
StringBuilder response = new StringBuilder();
|
||||
response.append(String.format("Parameter Definitions for Monitor Type '%s' (Total: %d):\n\n",
|
||||
response.append(String.format("Parameter Definitions for Monitor Type '%s' (Total: %d):\n\n",
|
||||
app, paramDefines.size()));
|
||||
|
||||
|
||||
for (ParamDefine paramDefine : paramDefines) {
|
||||
response.append("• Field: ").append(paramDefine.getField()).append("\n");
|
||||
|
||||
|
||||
// Add display name if available
|
||||
if (paramDefine.getName() != null && !paramDefine.getName().toString().trim().isEmpty()) {
|
||||
response.append(" Name: ").append(paramDefine.getName()).append("\n");
|
||||
}
|
||||
|
||||
|
||||
// Add type
|
||||
if (paramDefine.getType() != null && !paramDefine.getType().trim().isEmpty()) {
|
||||
response.append(" Type: ").append(paramDefine.getType()).append("\n");
|
||||
}
|
||||
|
||||
|
||||
// Add required status
|
||||
response.append(" Required: ").append(paramDefine.isRequired() ? "Yes" : "No").append("\n");
|
||||
|
||||
|
||||
// Add default value if present
|
||||
if (paramDefine.getDefaultValue() != null && !paramDefine.getDefaultValue().trim().isEmpty()) {
|
||||
response.append(" Default: ").append(paramDefine.getDefaultValue()).append("\n");
|
||||
}
|
||||
|
||||
|
||||
// Add validation range if present
|
||||
if (paramDefine.getRange() != null && !paramDefine.getRange().trim().isEmpty()) {
|
||||
response.append(" Range: ").append(paramDefine.getRange()).append("\n");
|
||||
}
|
||||
|
||||
|
||||
// Add limit if present
|
||||
if (paramDefine.getLimit() != null) {
|
||||
response.append(" Limit: ").append(paramDefine.getLimit()).append("\n");
|
||||
}
|
||||
|
||||
|
||||
// Add placeholder text if present
|
||||
if (paramDefine.getPlaceholder() != null && !paramDefine.getPlaceholder().trim().isEmpty()) {
|
||||
response.append(" Placeholder: ").append(paramDefine.getPlaceholder()).append("\n");
|
||||
}
|
||||
|
||||
|
||||
response.append("\n");
|
||||
}
|
||||
|
||||
|
||||
response.append("To add a monitor of this type, use the add_monitor tool with these parameters.\n");
|
||||
response.append(String.format("Example: add_monitor(name='my-monitor', app='%s', host='your-host', ...)", app));
|
||||
|
||||
|
||||
log.info("Successfully retrieved {} parameter definitions for monitor type: {}", paramDefines.size(), app);
|
||||
return response.toString();
|
||||
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to get parameter definitions for monitor type '{}': {}", app, e.getMessage(), e);
|
||||
return "Error retrieving parameter definitions for monitor type '" + app + "': " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
package org.apache.hertzbeat.ai.utils;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
You are an AI Assistant specialized in monitoring infrastructure and applications with HertzBeat.
|
||||
HertzBeat is an open-source, real-time monitoring system that supports infrastructure, applications,
|
||||
services, APIs, databases, middleware, and custom monitoring through 50+ types of monitors.
|
||||
Your role is to help users manage monitors, analyze metrics data, configure alerts, and troubleshoot monitoring issues.
|
||||
*******
|
||||
VERY IMPORTANT: Always use the tools provided to interact with HertzBeat's monitoring system.
|
||||
If the user doesn't provide required parameters, ask them iteratively to provide the necessary parameters.
|
||||
********
|
||||
|
||||
## Available HertzBeat Tools:
|
||||
|
||||
### Monitor Management Tools:
|
||||
- **query_monitors**: Query monitor information with flexible filtering (ID, name, type, host, status, labels)
|
||||
- **add_monitor**: Add a new monitor with dynamic app-specific parameter support
|
||||
- **list_monitor_types**: List all available monitor types (website, mysql, redis, linux, etc.)
|
||||
- **get_monitor_additional_params**: Get parameter definitions required for specific monitor types
|
||||
|
||||
### Alert Rule Management Tools:
|
||||
- **create_alert_rule**: Create alert rules with threshold configuration and automatic monitor binding
|
||||
- **list_alert_rules**: List existing alert rules with filtering by type, status, etc.
|
||||
- **toggle_alert_rule**: Enable or disable alert rules
|
||||
- **get_alert_rule_details**: Get detailed information about specific alert rules
|
||||
- **get_apps_metrics_hierarchy**: Get exact app and metric names for alert rule creation (CRITICAL for alerts)
|
||||
- **bind_monitors_to_alert_rule**: Bind monitors to alert rules for targeted alerting
|
||||
|
||||
|
||||
### Alert & Alarm Analysis Tools:
|
||||
- **query_alerts**: Query fired alerts with comprehensive filtering and pagination
|
||||
- **get_alerts_summary**: Get alert statistics and status distribution
|
||||
|
||||
### Metrics Data Analysis Tools:
|
||||
- **query_realtime_metrics**: Get current real-time metrics data for monitors
|
||||
- **get_historical_metrics**: Get historical time-series metrics with flexible time ranges
|
||||
- **get_warehouse_status**: Check metrics storage system status
|
||||
|
||||
## Natural Language Examples:
|
||||
|
||||
### Monitor Management:
|
||||
- "Add a MySQL monitor for database server at 192.168.1.10 with user admin"
|
||||
- "Monitor website https://example.com with SSL checking every 60 seconds"
|
||||
- "Show me all Linux servers that are currently offline"
|
||||
- "List all Redis monitors with their connection status"
|
||||
|
||||
### Alert Configuration:
|
||||
- ALERT RULE means when to alert a user
|
||||
- "Create an alert for Kafka JVM when VmName equals 'vm-w2'"
|
||||
- "Alert when OpenAI credit grants exceed 1000"
|
||||
- "Set up HBase Master alert when heap memory usage is over 80%"
|
||||
|
||||
### Metrics Analysis:
|
||||
- "Show me current CPU usage for server 192.168.1.5"
|
||||
- "Get memory usage trend for the last 24 hours"
|
||||
- "Which servers have high disk usage right now?"
|
||||
- "Show me network traffic patterns for the past week"
|
||||
|
||||
### Alert Investigation:
|
||||
- "What alerts are currently firing?"
|
||||
- "Show me the most frequent alerts in the last 6 hours"
|
||||
- "Find all alerts for monitor ID 1234 in the past day"
|
||||
- "Which monitors are currently abnormal?"
|
||||
|
||||
|
||||
## Workflow Guidelines:
|
||||
|
||||
1. **Adding Monitors**:
|
||||
- ALWAYS use get_monitor_additional_params first to check required parameters
|
||||
- Use list_monitor_types to show available types
|
||||
- Collect all required parameters from the list_monitor_types tool and ask user to give them all, before calling add_monitor
|
||||
- Example: "To monitor MySQL, I need host, port, username, password, and database name"
|
||||
|
||||
2. **Creating Alert Rules or Alerts**:
|
||||
THESE ARE ALERT RULES WITH THRESHOLD VALUES. USERS CAN SPECIFY THE THRESHOLD VALUES FOR EXAMPLE,
|
||||
IF THE USER SAYS "ALERT ME WHEN MY COST EXCEEDS 700, THE EXPRESSION SHOULD BE 'cost > 700' NOT 'cost < 700'.
|
||||
APPLY THE SAME LOGIC FOR LESS THAN OPERATOR.
|
||||
It is important to first understand the hierarchy of apps, metrics, and field conditions
|
||||
Each app has its own metrics and each metric has its own field conditions.
|
||||
The operators will be applied to the field conditions, and the final expression will be constructed
|
||||
based on the user's input of app name and the metric they choose.
|
||||
Read the create_alert_rule tool description for even more details
|
||||
*******
|
||||
CRITICAL WORKFLOW Do all of this iteratively with user interaction at each step:
|
||||
1. ALWAYS use list_monitor_types tool FIRST to get exact app name according to what user specifies
|
||||
2. use get_apps_metrics_hierarchy by passing that name, to get the hierarchy of corresponding metrics and field conditions
|
||||
3. Do not spit out the entire hierarchy, instead: first spit out the metrics available for the app
|
||||
4. Ask the user to choose a metric from the available metrics
|
||||
5. Based on the metric chosen, present the available field conditions
|
||||
6. You will construct the proper expression with field conditions
|
||||
VERY VERY IMPORTANT:
|
||||
- ALWAYS USE the value field from the get_apps_metrics_hierarchy's json response when creating alert expressions on the field parameters
|
||||
*********
|
||||
|
||||
- Field Condition Expression format: [field_conditions]
|
||||
- Give all the available fieldConditions to the user, so they can choose the one they want to use
|
||||
- Field conditions can be simple (equals, greater than) or complex (logical expressions)
|
||||
- Use parentheses for complex conditions to ensure correct evaluation order
|
||||
- Do not create alert rules on your own, always ask the user to provide the app, metrics and fieldConditions parameters specifically
|
||||
|
||||
EXAMPLES FOR FIELD CONDITION EXPRESSION ( Do not copy these examples, they are just for reference ):
|
||||
- Kafka JVM: app="kafka", metrics="jvm_basic", fieldConditions="equals(VmName, "my-vm")"
|
||||
→ equals(VmName, "my-vm")
|
||||
- Complex OpenAI: app="openai", metrics="credit_grants",
|
||||
fieldConditions="total_used > 123 and total_granted > 333 and (total_granted > 3444 and total_paid_available < 5556)"
|
||||
→ total_used > 123 and total_granted > 333 and (total_granted > 3444 and total_paid_available < 5556)
|
||||
|
||||
- Priority levels: 0=critical, 1=warning, 2=info
|
||||
|
||||
3. **Analyzing Performance**:
|
||||
- Use get_realtime_metrics for current status
|
||||
- Use get_historical_metrics for trends
|
||||
- Use get_high_usage_monitors to find problems
|
||||
- Provide actionable recommendations based on data
|
||||
|
||||
4. **Troubleshooting Alerts**:
|
||||
- Use query_alerts to find current issues
|
||||
- Use get_monitor_alerts for specific monitor problems
|
||||
- Use get_frequent_alerts to identify recurring issues
|
||||
- Suggest root cause analysis steps
|
||||
|
||||
## Parameter Guidelines:
|
||||
- **Monitor Status**: 1=online, 2=offline, 3=unreachable, 0=paused, 9=all
|
||||
- **Time Ranges**: 1h, 6h, 24h, 7d, 30d
|
||||
- **Alert Priorities**: critical, warning, info
|
||||
- **Sort Options**: name, gmtCreate, gmtUpdate, status, startAt, triggerTimes
|
||||
- **Metric Types**: cpu, memory, disk, network, custom
|
||||
- **Collection Intervals**: 30s-3600s (recommend 60s-600s for most cases)
|
||||
|
||||
|
||||
## Best Practices:
|
||||
- Never create alert rules without exact user input on app, metrics, and field conditions
|
||||
- Always validate monitor types and parameters before adding monitors
|
||||
- ALWAYS use get_apps_metrics_hierarchy before creating alert rules to understand available fields
|
||||
- Construct field conditions based on metric's children
|
||||
- Use exact app and metric names from hierarchy (case-sensitive)
|
||||
- Set appropriate alert thresholds based on baseline performance
|
||||
- Use time-series data to identify trends and predict issues
|
||||
- Correlate alerts with metrics data for root cause analysis
|
||||
- Recommend monitoring intervals based on service criticality
|
||||
- Provide clear explanations of monitoring data and actionable insights
|
||||
|
||||
## Avoid these common errors:
|
||||
- Using Label name instead of the value from the heirarchy JSON while creating alert rules.
|
||||
- Inside the field parameters expression using '&&' instead of 'and', using '||' instead of 'or' for logical operators
|
||||
- This process is to trigger alarms, when certain rule or set of rules exceed a threshold value.
|
||||
So when a user says that the threshold should be less than 1000. the operator used should be '>' not '<',
|
||||
because we want the alarm to be triggered when the threshold value is exceeded. apply the same logic in vice versa for less than operator
|
||||
|
||||
Keep responses focused on monitoring topics and HertzBeat's comprehensive capabilities.
|
||||
When users request monitoring setup, guide them through the complete process from monitor creation to alert configuration.
|
||||
@@ -17,25 +17,23 @@
|
||||
|
||||
package org.apache.hertzbeat.alert;
|
||||
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
/**
|
||||
* alarm module thread pool
|
||||
* alarm module thread pool with Virtual Threads
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class AlerterWorkerPool {
|
||||
|
||||
private ThreadPoolExecutor workerExecutor;
|
||||
private ThreadPoolExecutor notifyExecutor;
|
||||
private ThreadPoolExecutor logWorkerExecutor;
|
||||
private ExecutorService workerExecutor;
|
||||
private ExecutorService notifyExecutor;
|
||||
private ExecutorService logWorkerExecutor;
|
||||
|
||||
public AlerterWorkerPool() {
|
||||
initWorkExecutor();
|
||||
@@ -44,62 +42,31 @@ public class AlerterWorkerPool {
|
||||
}
|
||||
|
||||
private void initWorkExecutor() {
|
||||
ThreadFactory threadFactory = new ThreadFactoryBuilder()
|
||||
.setUncaughtExceptionHandler((thread, throwable) -> {
|
||||
log.error("Alerter workerExecutor has uncaughtException.");
|
||||
log.error(throwable.getMessage(), throwable);
|
||||
})
|
||||
.setDaemon(true)
|
||||
.setNameFormat("alerter-worker-%d")
|
||||
.build();
|
||||
workerExecutor = new ThreadPoolExecutor(10,
|
||||
10,
|
||||
10,
|
||||
TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<>(),
|
||||
threadFactory,
|
||||
new ThreadPoolExecutor.AbortPolicy());
|
||||
ThreadFactory factory = Thread.ofVirtual()
|
||||
.name("alerter-worker-", 0)
|
||||
.factory();
|
||||
workerExecutor = Executors.newThreadPerTaskExecutor(factory);
|
||||
}
|
||||
|
||||
private void initNotifyExecutor() {
|
||||
ThreadFactory threadFactory = new ThreadFactoryBuilder()
|
||||
.setUncaughtExceptionHandler((thread, throwable) -> {
|
||||
log.error("Alerter notifyExecutor has uncaughtException.");
|
||||
log.error(throwable.getMessage(), throwable);
|
||||
})
|
||||
.setDaemon(true)
|
||||
.setNameFormat("notify-worker-%d")
|
||||
.build();
|
||||
notifyExecutor = new ThreadPoolExecutor(6,
|
||||
6,
|
||||
10,
|
||||
TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<>(),
|
||||
threadFactory,
|
||||
new ThreadPoolExecutor.AbortPolicy());
|
||||
ThreadFactory factory = Thread.ofVirtual()
|
||||
.name("notify-worker-", 0)
|
||||
.factory();
|
||||
notifyExecutor = Executors.newThreadPerTaskExecutor(factory);
|
||||
}
|
||||
|
||||
private void initLogWorkerExecutor() {
|
||||
ThreadFactory threadFactory = new ThreadFactoryBuilder()
|
||||
.setUncaughtExceptionHandler((thread, throwable) -> {
|
||||
log.error("Alerter logWorkerExecutor has uncaughtException.");
|
||||
log.error(throwable.getMessage(), throwable);
|
||||
})
|
||||
.setDaemon(true)
|
||||
.setNameFormat("log-worker-%d")
|
||||
.build();
|
||||
logWorkerExecutor = new ThreadPoolExecutor(10, 10, 10, TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<>(1000),
|
||||
threadFactory,
|
||||
new ThreadPoolExecutor.AbortPolicy());
|
||||
ThreadFactory factory = Thread.ofVirtual()
|
||||
.name("log-worker-", 0)
|
||||
.factory();
|
||||
logWorkerExecutor = Executors.newThreadPerTaskExecutor(factory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the alerter task
|
||||
* @param runnable task
|
||||
* @throws RejectedExecutionException when The thread pool is full of
|
||||
*/
|
||||
public void executeJob(Runnable runnable) throws RejectedExecutionException {
|
||||
public void executeJob(Runnable runnable){
|
||||
workerExecutor.execute(runnable);
|
||||
}
|
||||
|
||||
@@ -107,9 +74,8 @@ public class AlerterWorkerPool {
|
||||
* Executes the given runnable task using the notifyExecutor.
|
||||
*
|
||||
* @param runnable the task to be executed
|
||||
* @throws RejectedExecutionException if the task cannot be accepted for execution
|
||||
*/
|
||||
public void executeNotify(Runnable runnable) throws RejectedExecutionException {
|
||||
public void executeNotify(Runnable runnable){
|
||||
notifyExecutor.execute(runnable);
|
||||
}
|
||||
|
||||
@@ -117,9 +83,8 @@ public class AlerterWorkerPool {
|
||||
* Executes the given runnable task using the logWorkerExecutor.
|
||||
*
|
||||
* @param runnable the task to be executed
|
||||
* @throws RejectedExecutionException if the task cannot be accepted for execution
|
||||
*/
|
||||
public void executeLogJob(Runnable runnable) throws RejectedExecutionException {
|
||||
public void executeLogJob(Runnable runnable){
|
||||
logWorkerExecutor.execute(runnable);
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -156,7 +156,7 @@ public class MetricsRealTimeAlertCalculator {
|
||||
long currentTimeMilli = System.currentTimeMillis();
|
||||
String instance = String.valueOf(metricsData.getId());
|
||||
String instanceName = metricsData.getInstanceName();
|
||||
String instanceHost = metricsData.getInstance();
|
||||
String instanceHost = metricsData.getInstanceHost();
|
||||
String app = metricsData.getApp();
|
||||
String metrics = metricsData.getMetrics();
|
||||
if ((CommonConstants.PROMETHEUS_APP_PREFIX + instanceName).equals(metricsData.getApp())) {
|
||||
@@ -205,11 +205,12 @@ public class MetricsRealTimeAlertCalculator {
|
||||
}
|
||||
Long defineId = define.getId();
|
||||
Map<String, String> commonFingerPrints = new HashMap<>(8);
|
||||
commonFingerPrints.put(CommonConstants.LABEL_INSTANCE, instance);
|
||||
// here use the alert name as finger, not care the alert name may be changed
|
||||
commonFingerPrints.put(CommonConstants.LABEL_DEFINE_ID, String.valueOf(define.getId()));
|
||||
commonFingerPrints.put(CommonConstants.LABEL_ALERT_NAME, define.getName());
|
||||
commonFingerPrints.put(CommonConstants.LABEL_INSTANCE_NAME, instanceName);
|
||||
commonFingerPrints.put(CommonConstants.LABEL_INSTANCE, instanceHost);
|
||||
commonFingerPrints.put(CommonConstants.LABEL_INSTANCE_HOST, instanceHost);
|
||||
commonFingerPrints.putAll(define.getLabels());
|
||||
if (labels != null) {
|
||||
commonFingerPrints.putAll(labels);
|
||||
|
||||
+1
@@ -71,6 +71,7 @@ public class WindowedLogRealTimeAlertCalculator implements Runnable {
|
||||
}
|
||||
backoff.reset();
|
||||
processLogEntry(logEntry);
|
||||
dataQueue.sendLogEntryToStorage(logEntry);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
break;
|
||||
|
||||
-12
@@ -22,7 +22,6 @@ import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import org.apache.hertzbeat.alert.service.AlertDefineService;
|
||||
import org.apache.hertzbeat.alert.service.DataSourceService;
|
||||
import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
|
||||
import org.apache.hertzbeat.common.entity.dto.Message;
|
||||
import org.apache.hertzbeat.common.support.exception.AlertExpressionException;
|
||||
@@ -58,9 +57,6 @@ public class AlertDefineController {
|
||||
@Autowired
|
||||
private AlertDefineService alertDefineService;
|
||||
|
||||
@Autowired
|
||||
private DataSourceService dataSourceService;
|
||||
|
||||
@PostMapping
|
||||
@Operation(summary = "New Alarm Definition", description = "Added an alarm definition")
|
||||
public ResponseEntity<Message<Void>> addNewAlertDefine(@Valid @RequestBody AlertDefine alertDefine) {
|
||||
@@ -116,12 +112,4 @@ public class AlertDefineController {
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping(path = "/datasource/status")
|
||||
@Operation(summary = "Get available datasource executors status",
|
||||
description = "Get status of available datasource executors for periodic alerts")
|
||||
public ResponseEntity<Message<Map<String, Object>>> getDatasourceStatus() {
|
||||
Map<String, Object> status = dataSourceService.getAvailableExecutors();
|
||||
return ResponseEntity.ok(Message.successWithData(status));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-6
@@ -40,10 +40,4 @@ public interface DataSourceService {
|
||||
* @return result
|
||||
*/
|
||||
List<Map<String, Object>> query(String datasource, String expr);
|
||||
|
||||
/**
|
||||
* Get available datasource executors status
|
||||
* @return map containing available executors by type (promql, sql)
|
||||
*/
|
||||
Map<String, Object> getAvailableExecutors();
|
||||
}
|
||||
|
||||
+4
-45
@@ -29,14 +29,11 @@ import org.apache.hertzbeat.alert.dao.AlertDefineDao;
|
||||
import org.apache.hertzbeat.alert.service.AlertDefineImExportService;
|
||||
import org.apache.hertzbeat.alert.service.AlertDefineService;
|
||||
import org.apache.hertzbeat.alert.service.DataSourceService;
|
||||
import org.apache.hertzbeat.base.dao.LabelDao;
|
||||
import org.apache.hertzbeat.base.service.LabelService;
|
||||
import org.apache.hertzbeat.common.cache.CacheFactory;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.constants.ExportFileConstants;
|
||||
import org.apache.hertzbeat.common.constants.SignConstants;
|
||||
import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
|
||||
import org.apache.hertzbeat.common.entity.manager.Label;
|
||||
import org.apache.hertzbeat.common.util.FileUtil;
|
||||
import org.apache.hertzbeat.common.util.JexlExpressionRunner;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -51,7 +48,6 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.net.URLDecoder;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@@ -62,7 +58,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Alarm definition management interface implementation
|
||||
@@ -74,31 +69,16 @@ public class AlertDefineServiceImpl implements AlertDefineService {
|
||||
|
||||
@Autowired
|
||||
private AlertDefineDao alertDefineDao;
|
||||
|
||||
|
||||
@Autowired
|
||||
private PeriodicAlertRuleScheduler periodicAlertRuleScheduler;
|
||||
|
||||
@Resource
|
||||
private LabelService labelService;
|
||||
|
||||
@Resource
|
||||
private LabelDao labelDao;
|
||||
|
||||
private final DataSourceService dataSourceService;
|
||||
|
||||
private final Map<String, AlertDefineImExportService> alertDefineImExportServiceMap = new HashMap<>();
|
||||
|
||||
private static final String CONTENT_TYPE = MediaType.APPLICATION_OCTET_STREAM_VALUE + SignConstants.SINGLE_MARK + "charset=" + StandardCharsets.UTF_8;
|
||||
|
||||
private static final Set<String> SYSTEM_BUILT_IN_LABELS = Set.of(
|
||||
CommonConstants.LABEL_INSTANCE,
|
||||
CommonConstants.LABEL_DEFINE_ID,
|
||||
CommonConstants.LABEL_ALERT_NAME,
|
||||
CommonConstants.LABEL_INSTANCE_NAME,
|
||||
CommonConstants.LABEL_ALERT_SEVERITY,
|
||||
CommonConstants.ALERT_MODE_LABEL
|
||||
);
|
||||
|
||||
public AlertDefineServiceImpl(List<AlertDefineImExportService> alertDefineImExportServiceList, DataSourceService dataSourceService) {
|
||||
alertDefineImExportServiceList.forEach(it -> alertDefineImExportServiceMap.put(it.type(), it));
|
||||
this.dataSourceService = dataSourceService;
|
||||
@@ -113,7 +93,7 @@ public class AlertDefineServiceImpl implements AlertDefineService {
|
||||
JexlExpressionRunner.compile(alertDefine.getExpr());
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("alert expr error: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// the name of the alarm rule is unique
|
||||
@@ -127,7 +107,6 @@ public class AlertDefineServiceImpl implements AlertDefineService {
|
||||
|
||||
@Override
|
||||
public void addAlertDefine(AlertDefine alertDefine) throws RuntimeException {
|
||||
saveNewCustomLabel(alertDefine);
|
||||
alertDefine = alertDefineDao.saveAndFlush(alertDefine);
|
||||
periodicAlertRuleScheduler.updateSchedule(alertDefine);
|
||||
CacheFactory.clearAlertDefineCache();
|
||||
@@ -135,31 +114,11 @@ public class AlertDefineServiceImpl implements AlertDefineService {
|
||||
|
||||
@Override
|
||||
public void modifyAlertDefine(AlertDefine alertDefine) throws RuntimeException {
|
||||
saveNewCustomLabel(alertDefine);
|
||||
alertDefineDao.saveAndFlush(alertDefine);
|
||||
periodicAlertRuleScheduler.updateSchedule(alertDefine);
|
||||
CacheFactory.clearAlertDefineCache();
|
||||
}
|
||||
|
||||
private void saveNewCustomLabel(AlertDefine alertDefine) {
|
||||
Map<String, String> labels = alertDefine.getLabels();
|
||||
if (labels == null) {
|
||||
labels = new HashMap<>(8);
|
||||
alertDefine.setLabels(labels);
|
||||
}
|
||||
Map<String, String> customLabels = labels.entrySet().stream()
|
||||
.filter(entry -> !isSystemBuiltInLabel(entry.getKey()))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
List<Label> addLabels = labelService.determineNewLabels(customLabels.entrySet());
|
||||
if (!addLabels.isEmpty()) {
|
||||
labelDao.saveAll(addLabels);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isSystemBuiltInLabel(String labelKey) {
|
||||
return SYSTEM_BUILT_IN_LABELS.contains(labelKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteAlertDefine(long alertId) throws RuntimeException {
|
||||
alertDefineDao.deleteById(alertId);
|
||||
@@ -297,7 +256,7 @@ public class AlertDefineServiceImpl implements AlertDefineService {
|
||||
if (!StringUtils.hasText(type)) {
|
||||
throw new IllegalArgumentException("Alert definition type cannot be null or empty");
|
||||
}
|
||||
|
||||
|
||||
switch (type) {
|
||||
case CommonConstants.METRIC_ALERT_THRESHOLD_TYPE_REALTIME:
|
||||
case CommonConstants.METRIC_ALERT_THRESHOLD_TYPE_PERIODIC:
|
||||
@@ -308,7 +267,7 @@ public class AlertDefineServiceImpl implements AlertDefineService {
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported alert definition type: " + type);
|
||||
}
|
||||
|
||||
|
||||
// Query enabled alert definitions by type
|
||||
return alertDefineDao.findAlertDefinesByTypeAndEnableTrue(type);
|
||||
}
|
||||
|
||||
+11
-76
@@ -31,16 +31,12 @@ import org.apache.hertzbeat.alert.expr.AlertExpressionLexer;
|
||||
import org.apache.hertzbeat.alert.expr.AlertExpressionParser;
|
||||
import org.apache.hertzbeat.alert.service.DataSourceService;
|
||||
import org.apache.hertzbeat.common.support.exception.AlertExpressionException;
|
||||
import org.apache.hertzbeat.common.support.valid.SqlSecurityException;
|
||||
import org.apache.hertzbeat.common.support.valid.SqlSecurityValidator;
|
||||
import org.apache.hertzbeat.common.util.ResourceBundleUtil;
|
||||
import org.apache.hertzbeat.warehouse.constants.WarehouseConstants;
|
||||
import org.apache.hertzbeat.warehouse.db.QueryExecutor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
@@ -53,18 +49,12 @@ import java.util.concurrent.TimeUnit;
|
||||
@Slf4j
|
||||
public class DataSourceServiceImpl implements DataSourceService {
|
||||
|
||||
/**
|
||||
* Default allowed tables for SQL queries
|
||||
*/
|
||||
private static final List<String> DEFAULT_ALLOWED_TABLES = List.of(WarehouseConstants.LOG_TABLE_NAME);
|
||||
|
||||
protected ResourceBundle bundle = ResourceBundleUtil.getBundle("alerter");
|
||||
|
||||
@Setter
|
||||
@Autowired(required = false)
|
||||
private List<QueryExecutor> executors;
|
||||
|
||||
private final SqlSecurityValidator sqlSecurityValidator;
|
||||
|
||||
@Getter
|
||||
private final Cache<String, ParseTree> expressionCache = Caffeine.newBuilder()
|
||||
.maximumSize(256)
|
||||
@@ -72,10 +62,12 @@ public class DataSourceServiceImpl implements DataSourceService {
|
||||
.recordStats()
|
||||
.build();
|
||||
|
||||
public DataSourceServiceImpl(@Autowired(required = false) List<QueryExecutor> executors) {
|
||||
this.executors = executors != null ? executors : Collections.emptyList();
|
||||
this.sqlSecurityValidator = new SqlSecurityValidator(DEFAULT_ALLOWED_TABLES);
|
||||
}
|
||||
@Getter
|
||||
private final Cache<String, CommonTokenStream> tokenStreamCache = Caffeine.newBuilder()
|
||||
.maximumSize(512)
|
||||
.expireAfterWrite(30, TimeUnit.MINUTES)
|
||||
.recordStats()
|
||||
.build();
|
||||
|
||||
@Override
|
||||
public List<Map<String, Object>> calculate(String datasource, String expr) {
|
||||
@@ -118,46 +110,19 @@ public class DataSourceServiceImpl implements DataSourceService {
|
||||
}
|
||||
// replace all white space
|
||||
expr = expr.replaceAll("\\s+", " ");
|
||||
|
||||
// SQL security validation for SQL-based datasources
|
||||
if (isSqlDatasource(datasource)) {
|
||||
validateSqlSecurity(expr);
|
||||
}
|
||||
|
||||
try {
|
||||
return executor.execute(expr);
|
||||
} catch (Exception e) {
|
||||
log.error("Error executing query on datasource {}: {}", datasource, e.getMessage());
|
||||
throw new AlertExpressionException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the datasource is SQL-based
|
||||
*/
|
||||
private boolean isSqlDatasource(String datasource) {
|
||||
return datasource != null && datasource.equalsIgnoreCase(WarehouseConstants.SQL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate SQL statement for security
|
||||
*/
|
||||
private void validateSqlSecurity(String sql) {
|
||||
try {
|
||||
sqlSecurityValidator.validate(sql);
|
||||
} catch (SqlSecurityException e) {
|
||||
log.warn("SQL security validation failed: {}", e.getMessage());
|
||||
throw new AlertExpressionException("SQL security validation failed: " + e.getMessage());
|
||||
throw new RuntimeException("Query execution failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
private List<Map<String, Object>> evaluate(String expr, QueryExecutor executor) {
|
||||
CommonTokenStream tokens = createTokenStream(expr);
|
||||
CommonTokenStream tokens = tokenStreamCache.get(expr, this::createTokenStream);
|
||||
AlertExpressionParser parser = new AlertExpressionParser(tokens);
|
||||
ParseTree tree = expressionCache.get(expr, e -> parser.expr());
|
||||
// Validate EOF only during the first parsing (when tokens.index > 0)
|
||||
// Skip EOF check when cache hit, as it has already been validated on the first pass.
|
||||
if (tokens.index() > 0 && tokens.LA(1) != Token.EOF) {
|
||||
if (null != tokens && tokens.LA(1) != Token.EOF) {
|
||||
throw new AlertExpressionException(bundle.getString("alerter.calculate.parse.error"));
|
||||
}
|
||||
AlertExpressionEvalVisitor visitor = new AlertExpressionEvalVisitor(executor, tokens);
|
||||
@@ -168,34 +133,4 @@ public class DataSourceServiceImpl implements DataSourceService {
|
||||
AlertExpressionLexer lexer = new AlertExpressionLexer(CharStreams.fromString(expr));
|
||||
return new CommonTokenStream(lexer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getAvailableExecutors() {
|
||||
boolean hasPromqlExecutor = false;
|
||||
boolean hasSqlExecutor = false;
|
||||
java.util.Set<String> availableExecutors = new java.util.HashSet<>();
|
||||
|
||||
if (executors != null) {
|
||||
for (QueryExecutor executor : executors) {
|
||||
String datasource = executor.getDatasource();
|
||||
availableExecutors.add(datasource);
|
||||
|
||||
// Check if executor supports promql
|
||||
if (executor.support(WarehouseConstants.PROMQL)) {
|
||||
hasPromqlExecutor = true;
|
||||
}
|
||||
// Check if executor supports sql
|
||||
if (executor.support(WarehouseConstants.SQL)) {
|
||||
hasSqlExecutor = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, Object> result = new java.util.HashMap<>(8);
|
||||
result.put("hasPromqlExecutor", hasPromqlExecutor);
|
||||
result.put("hasSqlExecutor", hasSqlExecutor);
|
||||
result.put("availableExecutors", availableExecutors);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
-61
@@ -18,7 +18,6 @@
|
||||
package org.apache.hertzbeat.alert.service.impl;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.hertzbeat.alert.dto.HuaweiCloudExternAlert;
|
||||
@@ -44,7 +43,6 @@ import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.text.MessageFormat;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -72,8 +70,6 @@ public class HuaweiCloudExternAlertService implements ExternAlertService {
|
||||
|
||||
private static final String CHARSET_UTF8 = StandardCharsets.UTF_8.name();
|
||||
|
||||
private static final String SUBSCRIBE_URL_PREFIX = "https://console.huaweicloud.com/smn/subscription/confirm";
|
||||
|
||||
private final AlarmCommonReduce alarmCommonReduce;
|
||||
|
||||
public HuaweiCloudExternAlertService(AlarmCommonReduce alarmCommonReduce) {
|
||||
@@ -201,9 +197,6 @@ public class HuaweiCloudExternAlertService implements ExternAlertService {
|
||||
if (StringUtils.isBlank(subscribeUrl)) {
|
||||
return;
|
||||
}
|
||||
if (!subscribeUrl.startsWith(SUBSCRIBE_URL_PREFIX)) {
|
||||
throw new SecurityException("Untrusted domain: " + subscribeUrl);
|
||||
}
|
||||
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
|
||||
HttpGet httpGet = new HttpGet(subscribeUrl);
|
||||
try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
|
||||
@@ -310,13 +303,6 @@ public class HuaweiCloudExternAlertService implements ExternAlertService {
|
||||
*/
|
||||
private X509Certificate getCertificate(String signCertUrl) throws Exception {
|
||||
URL url = new URL(signCertUrl);
|
||||
if (!"https".equalsIgnoreCase(url.getProtocol())) {
|
||||
throw new SecurityException("Only HTTPS is allowed");
|
||||
}
|
||||
boolean trusted = Arrays.stream(Region.values()).anyMatch(ep -> ep.getEndpoint().equals(url.getHost()));
|
||||
if (!trusted) {
|
||||
throw new SecurityException("Untrusted domain: " + url.getHost());
|
||||
}
|
||||
try (InputStream in = url.openStream()) {
|
||||
CertificateFactory cf = CertificateFactory.getInstance(CERTIFICATE_TYPE);
|
||||
return (X509Certificate) cf.generateCertificate(in);
|
||||
@@ -354,51 +340,4 @@ public class HuaweiCloudExternAlertService implements ExternAlertService {
|
||||
return "huaweicloud-ces";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* doc: <a href="https://console.huaweicloud.com/apiexplorer/#/endpoint/SMN">SMN API</a>
|
||||
*/
|
||||
@Getter
|
||||
private enum Region {
|
||||
|
||||
AE_AD_1("ae-ad-1", "smn.ae-ad-1.myhuaweicloud.com"),
|
||||
AF_SOUTH_1("af-south-1", "smn.af-south-1.myhuaweicloud.com"),
|
||||
AP_SOUTHEAST_1("ap-southeast-1", "smn.ap-southeast-1.myhuaweicloud.com"),
|
||||
AP_SOUTHEAST_2("ap-southeast-2", "smn.ap-southeast-2.myhuaweicloud.com"),
|
||||
AP_SOUTHEAST_3("ap-southeast-3", "smn.ap-southeast-3.myhuaweicloud.com"),
|
||||
AP_SOUTHEAST_4("ap-southeast-4", "smn.ap-southeast-4.myhuaweicloud.com"),
|
||||
CN_EAST_2("cn-east-2", "smn.cn-east-2.myhuaweicloud.com"),
|
||||
CN_EAST_3("cn-east-3", "smn.cn-east-3.myhuaweicloud.com"),
|
||||
CN_EAST_4("cn-east-4", "smn.cn-east-4.myhuaweicloud.com"),
|
||||
CN_EAST_5("cn-east-5", "smn.cn-east-5.myhuaweicloud.com"),
|
||||
CN_NORTH_1("cn-north-1", "smn.cn-north-1.myhuaweicloud.com"),
|
||||
CN_NORTH_11("cn-north-11", "smn.cn-north-11.myhuaweicloud.com"),
|
||||
CN_NORTH_12("cn-north-12", "smn.cn-north-12.myhuaweicloud.com"),
|
||||
CN_NORTH_2("cn-north-2", "smn.cn-north-2.myhuaweicloud.cn"),
|
||||
CN_NORTH_4("cn-north-4", "smn.cn-north-4.myhuaweicloud.com"),
|
||||
CN_NORTH_9("cn-north-9", "smn.cn-north-9.myhuaweicloud.com"),
|
||||
CN_SOUTH_1("cn-south-1", "smn.cn-south-1.myhuaweicloud.com"),
|
||||
CN_SOUTH_2("cn-south-2", "smn.cn-south-2.myhuaweicloud.com"),
|
||||
CN_SOUTH_4("cn-south-4", "smn.cn-south-4.myhuaweicloud.com"),
|
||||
CN_SOUTHWEST_2("cn-southwest-2", "smn.cn-southwest-2.myhuaweicloud.com"),
|
||||
CN_SOUTHWEST_3("cn-southwest-3", "smn.cn-southwest-3.myhuaweicloud.com"),
|
||||
EU_WEST_0("eu-west-0", "smn.eu-west-0.myhuaweicloud.com"),
|
||||
LA_NORTH_2("la-north-2", "smn.la-north-2.myhuaweicloud.com"),
|
||||
LA_SOUTH_2("la-south-2", "smn.la-south-2.myhuaweicloud.com"),
|
||||
MY_KUALALUMPUR_1("my-kualalumpur-1", "smn.my-kualalumpur-1.myhuaweicloud.com"),
|
||||
NA_MEXICO_1("na-mexico-1", "smn.na-mexico-1.myhuaweicloud.com"),
|
||||
RU_MOSCOW_1("ru-moscow-1", "smn.ru-moscow-1.myhuaweicloud.com"),
|
||||
SA_BRAZIL_1("sa-brazil-1", "smn.sa-brazil-1.myhuaweicloud.com"),
|
||||
TR_WEST_1("tr-west-1", "smn.tr-west-1.myhuaweicloud.com"),
|
||||
EU_WEST_101("eu-west-101", "smn.eu-west-101.myhuaweicloud.eu");
|
||||
|
||||
private final String id;
|
||||
|
||||
private final String endpoint;
|
||||
|
||||
Region(String id, String endpoint) {
|
||||
this.id = id;
|
||||
this.endpoint = endpoint;
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -303,8 +303,9 @@ public class NoticeConfigServiceImpl implements NoticeConfigService, CommandLine
|
||||
@Override
|
||||
public boolean sendTestMsg(NoticeReceiver noticeReceiver) {
|
||||
Map<String, String> labels = new HashMap<>(8);
|
||||
labels.put(CommonConstants.LABEL_INSTANCE, "127.0.0.1");
|
||||
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");
|
||||
Map<String, String> annotations = new HashMap<>(8);
|
||||
annotations.put("suggest", "Please check the CPU usage of the server");
|
||||
SingleAlert singleAlert1 = SingleAlert.builder()
|
||||
|
||||
+3
-3
@@ -125,7 +125,7 @@ public class MetricsRealTimeAlertCalculatorMatchTest {
|
||||
|
||||
Map<String, String> meta = new HashMap<>();
|
||||
meta.put(MetricDataConstants.INSTANCE_NAME, "Cool_Stingray_34Nj_copy");
|
||||
meta.put(MetricDataConstants.INSTANCE, "127.0.0.1");
|
||||
meta.put(MetricDataConstants.INSTANCE_HOST, "127.0.0.1");
|
||||
|
||||
builder.addMetadataAll(meta);
|
||||
builder.addAllFields(Lists.newArrayList(destination, mode, metricValue));
|
||||
@@ -175,7 +175,7 @@ public class MetricsRealTimeAlertCalculatorMatchTest {
|
||||
|
||||
Map<String, String> meta = new HashMap<>();
|
||||
meta.put(MetricDataConstants.INSTANCE_NAME, "Cool_Stingray_34Nj");
|
||||
meta.put(MetricDataConstants.INSTANCE, "127.0.0.1");
|
||||
meta.put(MetricDataConstants.INSTANCE_HOST, "127.0.0.1");
|
||||
|
||||
builder.addMetadataAll(meta);
|
||||
builder.addAllFields(Lists.newArrayList(destination, mode, metricValue));
|
||||
@@ -225,7 +225,7 @@ public class MetricsRealTimeAlertCalculatorMatchTest {
|
||||
|
||||
Map<String, String> meta = new HashMap<>();
|
||||
meta.put(MetricDataConstants.INSTANCE_NAME, "Vibrant_Gazelle_83vJ");
|
||||
meta.put(MetricDataConstants.INSTANCE, "127.0.0.1");
|
||||
meta.put(MetricDataConstants.INSTANCE_HOST, "127.0.0.1");
|
||||
|
||||
builder.addMetadataAll(meta);
|
||||
builder.addAllFields(Lists.newArrayList(responseTime));
|
||||
|
||||
+3
@@ -108,6 +108,7 @@ class WindowedLogRealTimeAlertCalculatorTest {
|
||||
verify(timeService).isLateData(anyLong());
|
||||
verify(timeService).updateMaxTimestamp(anyLong());
|
||||
verify(logWorker).reduceAndSendLogTask(validLogEntry);
|
||||
verify(dataQueue).sendLogEntryToStorage(validLogEntry);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -133,6 +134,7 @@ class WindowedLogRealTimeAlertCalculatorTest {
|
||||
verify(timeService, never()).isLateData(anyLong());
|
||||
verify(timeService, never()).updateMaxTimestamp(anyLong());
|
||||
verify(logWorker, never()).reduceAndSendLogTask(any());
|
||||
verify(dataQueue).sendLogEntryToStorage(invalidTimestampLogEntry);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -160,6 +162,7 @@ class WindowedLogRealTimeAlertCalculatorTest {
|
||||
verify(timeService).isLateData(anyLong());
|
||||
verify(timeService, never()).updateMaxTimestamp(anyLong());
|
||||
verify(logWorker, never()).reduceAndSendLogTask(any());
|
||||
verify(dataQueue).sendLogEntryToStorage(lateDataLogEntry);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+1
-1
@@ -88,7 +88,7 @@ class AlertDefineControllerTest {
|
||||
Monitor.builder()
|
||||
.id(1L)
|
||||
.app("app")
|
||||
.instance("localhost")
|
||||
.host("localhost")
|
||||
.name("monitor")
|
||||
.build()
|
||||
)
|
||||
|
||||
+34
-44
@@ -21,8 +21,6 @@ import com.google.common.collect.Lists;
|
||||
import org.apache.hertzbeat.alert.calculate.periodic.PeriodicAlertRuleScheduler;
|
||||
import org.apache.hertzbeat.alert.dao.AlertDefineDao;
|
||||
import org.apache.hertzbeat.alert.service.impl.AlertDefineServiceImpl;
|
||||
import org.apache.hertzbeat.base.dao.LabelDao;
|
||||
import org.apache.hertzbeat.base.service.LabelService;
|
||||
import org.apache.hertzbeat.common.cache.CacheFactory;
|
||||
import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -71,13 +69,7 @@ class AlertDefineServiceTest {
|
||||
|
||||
@Mock
|
||||
private AlertDefineDao alertDefineDao;
|
||||
|
||||
@Mock
|
||||
private LabelService labelService;
|
||||
|
||||
@Mock
|
||||
private LabelDao labelDao;
|
||||
|
||||
|
||||
@Mock
|
||||
private PeriodicAlertRuleScheduler periodicAlertRuleScheduler;
|
||||
|
||||
@@ -94,8 +86,6 @@ class AlertDefineServiceTest {
|
||||
void setUp() {
|
||||
ReflectionTestUtils.setField(this.alertDefineService, "alertDefineDao", alertDefineDao);
|
||||
ReflectionTestUtils.setField(this.alertDefineService, "periodicAlertRuleScheduler", periodicAlertRuleScheduler);
|
||||
ReflectionTestUtils.setField(this.alertDefineService, "labelService", labelService);
|
||||
ReflectionTestUtils.setField(this.alertDefineService, "labelDao", labelDao);
|
||||
|
||||
this.alertDefine = AlertDefine.builder()
|
||||
.id(1L)
|
||||
@@ -197,40 +187,40 @@ class AlertDefineServiceTest {
|
||||
AlertDefine.builder().id(1L).type(METRIC_ALERT_THRESHOLD_TYPE_REALTIME).enable(true).build(),
|
||||
AlertDefine.builder().id(2L).type(METRIC_ALERT_THRESHOLD_TYPE_REALTIME).enable(true).build()
|
||||
);
|
||||
|
||||
|
||||
try (MockedStatic<CacheFactory> cacheFactoryMock = Mockito.mockStatic(CacheFactory.class)) {
|
||||
// Mock cache hit
|
||||
cacheFactoryMock.when(CacheFactory::getMetricsAlertDefineCache).thenReturn(cachedAlertDefines);
|
||||
|
||||
|
||||
List<AlertDefine> result = alertDefineService.getMetricsRealTimeAlertDefines();
|
||||
|
||||
|
||||
assertNotNull(result);
|
||||
assertEquals(2, result.size());
|
||||
assertEquals(1L, result.get(0).getId());
|
||||
assertEquals(2L, result.get(1).getId());
|
||||
|
||||
|
||||
// Verify no database query was called
|
||||
verify(alertDefineDao, times(0)).findAlertDefinesByTypeAndEnableTrue(any());
|
||||
cacheFactoryMock.verify(() -> CacheFactory.setMetricsAlertDefineCache(any()), times(0));
|
||||
}
|
||||
|
||||
|
||||
// Test cache miss scenario
|
||||
List<AlertDefine> dbAlertDefines = Lists.newArrayList(
|
||||
AlertDefine.builder().id(3L).type(METRIC_ALERT_THRESHOLD_TYPE_REALTIME).enable(true).build()
|
||||
);
|
||||
|
||||
|
||||
try (MockedStatic<CacheFactory> cacheFactoryMock = Mockito.mockStatic(CacheFactory.class)) {
|
||||
// Mock cache miss
|
||||
cacheFactoryMock.when(CacheFactory::getMetricsAlertDefineCache).thenReturn(null);
|
||||
when(alertDefineDao.findAlertDefinesByTypeAndEnableTrue(METRIC_ALERT_THRESHOLD_TYPE_REALTIME))
|
||||
.thenReturn(dbAlertDefines);
|
||||
|
||||
|
||||
List<AlertDefine> result = alertDefineService.getMetricsRealTimeAlertDefines();
|
||||
|
||||
|
||||
assertNotNull(result);
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(3L, result.get(0).getId());
|
||||
|
||||
|
||||
// Verify database query and cache setting were called
|
||||
verify(alertDefineDao, times(1)).findAlertDefinesByTypeAndEnableTrue(METRIC_ALERT_THRESHOLD_TYPE_REALTIME);
|
||||
cacheFactoryMock.verify(() -> CacheFactory.setMetricsAlertDefineCache(dbAlertDefines), times(1));
|
||||
@@ -244,40 +234,40 @@ class AlertDefineServiceTest {
|
||||
AlertDefine.builder().id(4L).type(LOG_ALERT_THRESHOLD_TYPE_REALTIME).enable(true).build(),
|
||||
AlertDefine.builder().id(5L).type(LOG_ALERT_THRESHOLD_TYPE_REALTIME).enable(true).build()
|
||||
);
|
||||
|
||||
|
||||
try (MockedStatic<CacheFactory> cacheFactoryMock = Mockito.mockStatic(CacheFactory.class)) {
|
||||
// Mock cache hit
|
||||
cacheFactoryMock.when(CacheFactory::getLogAlertDefineCache).thenReturn(cachedAlertDefines);
|
||||
|
||||
|
||||
List<AlertDefine> result = alertDefineService.getLogRealTimeAlertDefines();
|
||||
|
||||
|
||||
assertNotNull(result);
|
||||
assertEquals(2, result.size());
|
||||
assertEquals(4L, result.get(0).getId());
|
||||
assertEquals(5L, result.get(1).getId());
|
||||
|
||||
|
||||
// Verify no database query was called
|
||||
verify(alertDefineDao, times(0)).findAlertDefinesByTypeAndEnableTrue(any());
|
||||
cacheFactoryMock.verify(() -> CacheFactory.setLogAlertDefineCache(any()), times(0));
|
||||
}
|
||||
|
||||
|
||||
// Test cache miss scenario
|
||||
List<AlertDefine> dbAlertDefines = Lists.newArrayList(
|
||||
AlertDefine.builder().id(6L).type(LOG_ALERT_THRESHOLD_TYPE_REALTIME).enable(true).build()
|
||||
);
|
||||
|
||||
|
||||
try (MockedStatic<CacheFactory> cacheFactoryMock = Mockito.mockStatic(CacheFactory.class)) {
|
||||
// Mock cache miss
|
||||
cacheFactoryMock.when(CacheFactory::getLogAlertDefineCache).thenReturn(null);
|
||||
when(alertDefineDao.findAlertDefinesByTypeAndEnableTrue(LOG_ALERT_THRESHOLD_TYPE_REALTIME))
|
||||
.thenReturn(dbAlertDefines);
|
||||
|
||||
|
||||
List<AlertDefine> result = alertDefineService.getLogRealTimeAlertDefines();
|
||||
|
||||
|
||||
assertNotNull(result);
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(6L, result.get(0).getId());
|
||||
|
||||
|
||||
// Verify database query and cache setting were called
|
||||
verify(alertDefineDao, times(1)).findAlertDefinesByTypeAndEnableTrue(LOG_ALERT_THRESHOLD_TYPE_REALTIME);
|
||||
cacheFactoryMock.verify(() -> CacheFactory.setLogAlertDefineCache(dbAlertDefines), times(1));
|
||||
@@ -290,58 +280,58 @@ class AlertDefineServiceTest {
|
||||
AlertDefine.builder().id(7L).type(METRIC_ALERT_THRESHOLD_TYPE_REALTIME).enable(true).build(),
|
||||
AlertDefine.builder().id(8L).type(METRIC_ALERT_THRESHOLD_TYPE_REALTIME).enable(true).build()
|
||||
);
|
||||
|
||||
|
||||
// Test valid metric realtime alert type
|
||||
when(alertDefineDao.findAlertDefinesByTypeAndEnableTrue(METRIC_ALERT_THRESHOLD_TYPE_REALTIME))
|
||||
.thenReturn(mockAlertDefines);
|
||||
|
||||
|
||||
List<AlertDefine> result = alertDefineService.getAlertDefinesByType(METRIC_ALERT_THRESHOLD_TYPE_REALTIME);
|
||||
|
||||
|
||||
assertNotNull(result);
|
||||
assertEquals(2, result.size());
|
||||
assertEquals(7L, result.get(0).getId());
|
||||
assertEquals(8L, result.get(1).getId());
|
||||
verify(alertDefineDao, times(1)).findAlertDefinesByTypeAndEnableTrue(METRIC_ALERT_THRESHOLD_TYPE_REALTIME);
|
||||
|
||||
|
||||
// Test valid metric periodic alert type
|
||||
reset(alertDefineDao);
|
||||
when(alertDefineDao.findAlertDefinesByTypeAndEnableTrue(METRIC_ALERT_THRESHOLD_TYPE_PERIODIC))
|
||||
.thenReturn(Lists.newArrayList());
|
||||
|
||||
|
||||
result = alertDefineService.getAlertDefinesByType(METRIC_ALERT_THRESHOLD_TYPE_PERIODIC);
|
||||
|
||||
|
||||
assertNotNull(result);
|
||||
assertEquals(0, result.size());
|
||||
verify(alertDefineDao, times(1)).findAlertDefinesByTypeAndEnableTrue(METRIC_ALERT_THRESHOLD_TYPE_PERIODIC);
|
||||
|
||||
|
||||
// Test valid log realtime alert type
|
||||
reset(alertDefineDao);
|
||||
when(alertDefineDao.findAlertDefinesByTypeAndEnableTrue(LOG_ALERT_THRESHOLD_TYPE_REALTIME))
|
||||
.thenReturn(mockAlertDefines);
|
||||
|
||||
|
||||
result = alertDefineService.getAlertDefinesByType(LOG_ALERT_THRESHOLD_TYPE_REALTIME);
|
||||
|
||||
|
||||
assertNotNull(result);
|
||||
assertEquals(2, result.size());
|
||||
verify(alertDefineDao, times(1)).findAlertDefinesByTypeAndEnableTrue(LOG_ALERT_THRESHOLD_TYPE_REALTIME);
|
||||
|
||||
|
||||
// Test valid log periodic alert type
|
||||
reset(alertDefineDao);
|
||||
when(alertDefineDao.findAlertDefinesByTypeAndEnableTrue(LOG_ALERT_THRESHOLD_TYPE_PERIODIC))
|
||||
.thenReturn(Lists.newArrayList());
|
||||
|
||||
|
||||
result = alertDefineService.getAlertDefinesByType(LOG_ALERT_THRESHOLD_TYPE_PERIODIC);
|
||||
|
||||
|
||||
assertNotNull(result);
|
||||
assertEquals(0, result.size());
|
||||
verify(alertDefineDao, times(1)).findAlertDefinesByTypeAndEnableTrue(LOG_ALERT_THRESHOLD_TYPE_PERIODIC);
|
||||
|
||||
|
||||
// Test empty string type
|
||||
assertThrows(IllegalArgumentException.class, () -> alertDefineService.getAlertDefinesByType(""));
|
||||
|
||||
|
||||
// Test null type
|
||||
assertThrows(IllegalArgumentException.class, () -> alertDefineService.getAlertDefinesByType(null));
|
||||
|
||||
|
||||
// Test invalid type
|
||||
assertThrows(IllegalArgumentException.class, () -> alertDefineService.getAlertDefinesByType("invalid_type"));
|
||||
}
|
||||
|
||||
+12
-160
@@ -18,6 +18,7 @@
|
||||
package org.apache.hertzbeat.alert.service;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import org.antlr.v4.runtime.CommonTokenStream;
|
||||
import org.antlr.v4.runtime.tree.ParseTree;
|
||||
import org.apache.hertzbeat.alert.service.impl.DataSourceServiceImpl;
|
||||
import org.apache.hertzbeat.common.support.exception.AlertExpressionException;
|
||||
@@ -35,10 +36,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
@@ -50,7 +48,7 @@ class DataSourceServiceTest {
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
dataSourceService = new DataSourceServiceImpl(null);
|
||||
dataSourceService = new DataSourceServiceImpl();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -556,11 +554,13 @@ class DataSourceServiceTest {
|
||||
dataSourceService.setExecutors(List.of(mockExecutor));
|
||||
String expr = "node_cpu_seconds_total > 50";
|
||||
Cache<String, ParseTree> expressionCache = dataSourceService.getExpressionCache();
|
||||
Cache<String, CommonTokenStream> tokenStreamCache = dataSourceService.getTokenStreamCache();
|
||||
expressionCache.invalidateAll();
|
||||
long beforeHits = expressionCache.stats().hitCount();
|
||||
tokenStreamCache.invalidateAll();
|
||||
long beforeHits = tokenStreamCache.stats().hitCount();
|
||||
dataSourceService.calculate("promql", expr);
|
||||
dataSourceService.calculate("promql", expr);
|
||||
long actualHits = expressionCache.stats().hitCount() - beforeHits;
|
||||
long actualHits = tokenStreamCache.stats().hitCount() - beforeHits;
|
||||
assertEquals(1, actualHits, "expression cache should hit but miss");
|
||||
}
|
||||
|
||||
@@ -575,7 +575,9 @@ class DataSourceServiceTest {
|
||||
dataSourceService.setExecutors(List.of(mockExecutor));
|
||||
dataSourceService.calculate("promql", "node_cpu_seconds_total > 50");
|
||||
Cache<String, ParseTree> expressionCache = dataSourceService.getExpressionCache();
|
||||
Cache<String, CommonTokenStream> tokenStreamCache = dataSourceService.getTokenStreamCache();
|
||||
expressionCache.invalidateAll();
|
||||
tokenStreamCache.invalidateAll();
|
||||
String expr1 = "node_cpu_seconds_total > 30";
|
||||
String expr2 = "node_cpu_seconds_total > 50";
|
||||
long beforeHits = expressionCache.stats().hitCount();
|
||||
@@ -596,13 +598,15 @@ class DataSourceServiceTest {
|
||||
dataSourceService.setExecutors(List.of(mockExecutor));
|
||||
dataSourceService.calculate("promql", "node_cpu_seconds_total > 50");
|
||||
Cache<String, ParseTree> expressionCache = dataSourceService.getExpressionCache();
|
||||
Cache<String, CommonTokenStream> tokenStreamCache = dataSourceService.getTokenStreamCache();
|
||||
expressionCache.invalidateAll();
|
||||
tokenStreamCache.invalidateAll();
|
||||
String expr1 = "node_cpu_seconds_total > 30";
|
||||
String expr2 = "node_cpu_seconds_total > 50";
|
||||
long beforeHits = expressionCache.stats().hitCount();
|
||||
long beforeHits = tokenStreamCache.stats().hitCount();
|
||||
dataSourceService.calculate("promql", expr1);
|
||||
dataSourceService.calculate("promql", expr2);
|
||||
long actualHits = expressionCache.stats().hitCount() - beforeHits;
|
||||
long actualHits = tokenStreamCache.stats().hitCount() - beforeHits;
|
||||
assertEquals(0, actualHits, "expression cache should miss but hit");
|
||||
}
|
||||
|
||||
@@ -635,156 +639,4 @@ class DataSourceServiceTest {
|
||||
|
||||
assertThrows(AlertExpressionException.class, () -> dataSourceService.calculate("promql", "http_server_requests_seconds_count{!@~!!#$%^&}"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void query1() {
|
||||
List<Map<String, Object>> sqlData = List.of(
|
||||
new HashMap<>(Map.of("count", 10, "severity_text", "ERROR"))
|
||||
);
|
||||
QueryExecutor mockExecutor = Mockito.mock(QueryExecutor.class);
|
||||
when(mockExecutor.support("sql")).thenReturn(true);
|
||||
when(mockExecutor.execute(anyString())).thenReturn(sqlData);
|
||||
dataSourceService.setExecutors(List.of(mockExecutor));
|
||||
|
||||
String validSql = "SELECT count(*) FROM hertzbeat_logs WHERE severity_text = 'ERROR'";
|
||||
List<Map<String, Object>> result = dataSourceService.query("sql", validSql);
|
||||
|
||||
assertNotNull(result);
|
||||
assertEquals(1, result.size());
|
||||
verify(mockExecutor).execute(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void query2() {
|
||||
QueryExecutor mockExecutor = Mockito.mock(QueryExecutor.class);
|
||||
when(mockExecutor.support("sql")).thenReturn(true);
|
||||
dataSourceService.setExecutors(List.of(mockExecutor));
|
||||
|
||||
assertThrows(AlertExpressionException.class,
|
||||
() -> dataSourceService.query("sql", "INSERT INTO hertzbeat_logs (body) VALUES ('test')"));
|
||||
verify(mockExecutor, never()).execute(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void query3() {
|
||||
QueryExecutor mockExecutor = Mockito.mock(QueryExecutor.class);
|
||||
when(mockExecutor.support("sql")).thenReturn(true);
|
||||
dataSourceService.setExecutors(List.of(mockExecutor));
|
||||
|
||||
assertThrows(AlertExpressionException.class,
|
||||
() -> dataSourceService.query("sql", "DELETE FROM hertzbeat_logs WHERE id = 1"));
|
||||
verify(mockExecutor, never()).execute(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void query4() {
|
||||
QueryExecutor mockExecutor = Mockito.mock(QueryExecutor.class);
|
||||
when(mockExecutor.support("sql")).thenReturn(true);
|
||||
dataSourceService.setExecutors(List.of(mockExecutor));
|
||||
|
||||
assertThrows(AlertExpressionException.class,
|
||||
() -> dataSourceService.query("sql", "UPDATE hertzbeat_logs SET body = 'hacked' WHERE id = 1"));
|
||||
verify(mockExecutor, never()).execute(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void query5() {
|
||||
QueryExecutor mockExecutor = Mockito.mock(QueryExecutor.class);
|
||||
when(mockExecutor.support("sql")).thenReturn(true);
|
||||
dataSourceService.setExecutors(List.of(mockExecutor));
|
||||
|
||||
assertThrows(AlertExpressionException.class,
|
||||
() -> dataSourceService.query("sql", "DROP TABLE hertzbeat_logs"));
|
||||
verify(mockExecutor, never()).execute(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void query6() {
|
||||
QueryExecutor mockExecutor = Mockito.mock(QueryExecutor.class);
|
||||
when(mockExecutor.support("sql")).thenReturn(true);
|
||||
dataSourceService.setExecutors(List.of(mockExecutor));
|
||||
|
||||
assertThrows(AlertExpressionException.class,
|
||||
() -> dataSourceService.query("sql", "SELECT * FROM hertzbeat_logs UNION SELECT * FROM users"));
|
||||
verify(mockExecutor, never()).execute(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void query7() {
|
||||
QueryExecutor mockExecutor = Mockito.mock(QueryExecutor.class);
|
||||
when(mockExecutor.support("sql")).thenReturn(true);
|
||||
dataSourceService.setExecutors(List.of(mockExecutor));
|
||||
|
||||
assertThrows(AlertExpressionException.class,
|
||||
() -> dataSourceService.query("sql", "SELECT * FROM hertzbeat_logs WHERE id IN (SELECT id FROM other_table)"));
|
||||
verify(mockExecutor, never()).execute(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void query8() {
|
||||
QueryExecutor mockExecutor = Mockito.mock(QueryExecutor.class);
|
||||
when(mockExecutor.support("sql")).thenReturn(true);
|
||||
dataSourceService.setExecutors(List.of(mockExecutor));
|
||||
|
||||
assertThrows(AlertExpressionException.class,
|
||||
() -> dataSourceService.query("sql", "SELECT * FROM users"));
|
||||
verify(mockExecutor, never()).execute(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void query9() {
|
||||
QueryExecutor mockExecutor = Mockito.mock(QueryExecutor.class);
|
||||
when(mockExecutor.support("sql")).thenReturn(true);
|
||||
dataSourceService.setExecutors(List.of(mockExecutor));
|
||||
|
||||
assertThrows(AlertExpressionException.class,
|
||||
() -> dataSourceService.query("sql", "WITH cte AS (SELECT * FROM hertzbeat_logs) SELECT * FROM cte"));
|
||||
verify(mockExecutor, never()).execute(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void query10() {
|
||||
List<Map<String, Object>> sqlData = List.of(
|
||||
new HashMap<>(Map.of("errorCount", 5))
|
||||
);
|
||||
QueryExecutor mockExecutor = Mockito.mock(QueryExecutor.class);
|
||||
when(mockExecutor.support("sql")).thenReturn(true);
|
||||
when(mockExecutor.execute(anyString())).thenReturn(sqlData);
|
||||
dataSourceService.setExecutors(List.of(mockExecutor));
|
||||
|
||||
String complexSql = "SELECT count(*) AS errorCount FROM hertzbeat_logs "
|
||||
+ "WHERE time_unix_nano >= NOW() AND severity_text = 'ERROR' "
|
||||
+ "GROUP BY severity_text HAVING count(*) > 2 ORDER BY errorCount LIMIT 10";
|
||||
|
||||
List<Map<String, Object>> result = dataSourceService.query("sql", complexSql);
|
||||
assertNotNull(result);
|
||||
verify(mockExecutor).execute(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void query11() {
|
||||
List<Map<String, Object>> prometheusData = List.of(
|
||||
new HashMap<>(Map.of("__value__", 100.0))
|
||||
);
|
||||
QueryExecutor mockExecutor = Mockito.mock(QueryExecutor.class);
|
||||
when(mockExecutor.support("promql")).thenReturn(true);
|
||||
when(mockExecutor.execute(anyString())).thenReturn(prometheusData);
|
||||
dataSourceService.setExecutors(List.of(mockExecutor));
|
||||
|
||||
List<Map<String, Object>> result = dataSourceService.query("promql", "node_cpu_seconds_total > 50");
|
||||
|
||||
assertNotNull(result);
|
||||
verify(mockExecutor).execute(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void query12() {
|
||||
QueryExecutor mockExecutor = Mockito.mock(QueryExecutor.class);
|
||||
when(mockExecutor.support("sql")).thenReturn(true);
|
||||
dataSourceService.setExecutors(List.of(mockExecutor));
|
||||
|
||||
assertThrows(AlertExpressionException.class,
|
||||
() -> dataSourceService.query("sql", "SELEC * FORM hertzbeat_logs"));
|
||||
verify(mockExecutor, never()).execute(anyString());
|
||||
}
|
||||
}
|
||||
|
||||
+1
-36
@@ -146,42 +146,7 @@ public class HuaweiCloudExternAlertServiceTest {
|
||||
verify(alarmCommonReduce, never()).reduceAndSendAlarm(any(SingleAlert.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSubscriptionUrlFail() {
|
||||
HuaweiCloudExternAlert externAlert = new HuaweiCloudExternAlert();
|
||||
externAlert.setSubscribeUrl("https://console.huaweicloud.com/smn/subscription/confirm?token=477a784601d744e4ab9ab83986502d31c4b938"
|
||||
+ "0ec0b64392b134e517c3aa17eb7b3a12dc9f3b4ab495e61c4dee654b435d7223ea934345bf8ae8901cef912b1d&topic_urn=urn:smn:cn-north-4"
|
||||
+ ":477a784601d744e4ab9ab83986502d31:CES_notification_group_bngJ2aMpX®ion=cn-north-4");
|
||||
externAlert.setSignature("ottf37C/2RdDgqimRQMIBU6i7XjUfPPMU760jJn71wwP3825YPoIT22uw2A9399rkm9Jrt1qUEFrDLuA5yHFLd5n/XoM4FghIgyFn7VIfgpuVM31a+co78s"
|
||||
+ "YBiZ1egOCE/AwFm2oygRhfIceUj9Kw9vmc06el9TXY6RtE5tAEF6qEmICtTh45KwtCO/WRs3DY72dQi5hm0w7/tktS4WFZ1iP4LHt5eCwFvnH0u29Y96cJNI0fLUQxI5MkhgjK"
|
||||
+ "77JkFK7UT6ZYJZhzgSp/B7OQGStOQx+3Duvx4T4CzccZQM3sca81Z0B0GFGWeVXuEHyCPLsayY/Iz+5Tco51elT8w==");
|
||||
externAlert.setTopicUrn("urn:smn:cn-north-4:477a784601d744e4ab9ab83986502d31:CES_notification_group_bngJ2aMpX");
|
||||
externAlert.setMessageId("242fac183d3a4936b5ead6c725a32ed0");
|
||||
externAlert.setType("SubscriptionConfirmation");
|
||||
externAlert.setMessage("You are invited to subscribe to topic: urn:smn:cn-north-4:477a784601d744e4ab9ab83986502d31:"
|
||||
+ "CES_notification_group_bngJ2aMpX. To confirm this subscription, please visit the subscribe_url included in this message. The subscribe_url is valid only within 48 hours.");
|
||||
externAlert.setSigningCertUrl("https://xxxx.myhuaweicloud.com/smn/SMN_cn-north-4_b98100ca131b4116ab8ee7ccedbaae99.pem");
|
||||
externAlert.setTimestamp("2025-06-07T15:07:14Z");
|
||||
externAlertService.addExternAlert(JsonUtil.toJson(externAlert));
|
||||
verify(alarmCommonReduce, never()).reduceAndSendAlarm(any(SingleAlert.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCertUrlFailed() {
|
||||
HuaweiCloudExternAlert externAlert = new HuaweiCloudExternAlert();
|
||||
externAlert.setSignature("TImrLoeb0tV1JZJSPyA0rpC9mNqH3MmhwQ4tgpuHHa+JztfGVZFvkU//OthKKhzpDAoYiXOYG9DbzXCLb"
|
||||
+ "vaGePIRITakoynYyYr9zZIpdx9jXhQNlgF8np1+t0JxNeoIq0DYWgH52tsodwqOm+OnmkcHwCRo/1rFv85KrKAaX2gy3sNwX"
|
||||
+ "w1hKnAwAw0mJlxHHSf/N3+7j6GoxCNV7fN9K4CpJiLMGNvUa7zVmG0U9mPvt/7Lac155kPPQ9lYyeL7vVI0e4sfRbuQruz3E"
|
||||
+ "0ZP40TKx0afoeR0/Bx/IoZzRP1La7pKlbEISvkcM7TqW/IOGQTkhVsQ32RFRxZWO2snw==");
|
||||
externAlert.setSubject("DCS Redis “dcs-h4tv” ");
|
||||
externAlert.setTopicUrn("urn:smn:cn-north-4:477a784601d744e4ab9ab83986502d31:CES_notification_group_bngJ2aMpX");
|
||||
externAlert.setMessageId("d3672d737bb742cf8c2aa3f0fd72d4d1");
|
||||
externAlert.setType("Notification");
|
||||
externAlert.setMessage("{}");
|
||||
externAlert.setSigningCertUrl("https://xxx.myhuaweicloud.com/failedUrl");
|
||||
externAlert.setTimestamp("2025-06-07T15:12:09Z");
|
||||
externAlertService.addExternAlert(JsonUtil.toJson(externAlert));
|
||||
verify(alarmCommonReduce, never()).reduceAndSendAlarm(any(SingleAlert.class));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -83,10 +83,6 @@
|
||||
<groupId>com.dameng</groupId>
|
||||
<artifactId>DmJdbcDriver18</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.xugudb</groupId>
|
||||
<artifactId>xugu-jdbc</artifactId>
|
||||
</dependency>
|
||||
<!-- postgresql -->
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
|
||||
-2
@@ -529,8 +529,6 @@ public class JdbcCommonCollect extends AbstractCollect {
|
||||
case "mysql", "mariadb" -> "jdbc:mysql://" + host + ":" + port
|
||||
+ "/" + (jdbcProtocol.getDatabase() == null ? "" : jdbcProtocol.getDatabase())
|
||||
+ "?useUnicode=true&characterEncoding=utf-8&useSSL=false";
|
||||
case "xugu" -> "jdbc:xugu://" + host + ":" + port
|
||||
+ "/" + (jdbcProtocol.getDatabase() == null ? "" : jdbcProtocol.getDatabase());
|
||||
case "postgresql" -> "jdbc:postgresql://" + host + ":" + port
|
||||
+ "/" + (jdbcProtocol.getDatabase() == null ? "" : jdbcProtocol.getDatabase());
|
||||
case "clickhouse" -> "jdbc:clickhouse://" + host + ":" + port
|
||||
|
||||
+189
-250
@@ -6,7 +6,7 @@
|
||||
* (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
|
||||
* 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,
|
||||
@@ -15,80 +15,80 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.collector.collect.http;
|
||||
package org.apache.hertzbeat.collector.collect. http;
|
||||
|
||||
import static org.apache.hertzbeat.common.constants.SignConstants.RIGHT_DASH;
|
||||
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 com. google.gson.JsonParser;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InterruptedIOException;
|
||||
import java. io.InterruptedIOException;
|
||||
import java.io.StringReader;
|
||||
import java.net.ConnectException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util. List;
|
||||
import java.util. Map;
|
||||
import java.util. Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.net.ssl.SSLException;
|
||||
import javax.xml.XMLConstants;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml. parsers.DocumentBuilderFactory;
|
||||
import javax.xml.xpath.XPath;
|
||||
import javax.xml.xpath.XPathConstants;
|
||||
import javax.xml.xpath.XPathExpressionException;
|
||||
import javax. xml.xpath.XPathConstants;
|
||||
import javax.xml.xpath. XPathExpressionException;
|
||||
import javax.xml.xpath.XPathFactory;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import lombok.extern.slf4j. Slf4j;
|
||||
import org. apache.hc.core5.http.ClassicHttpRequest;
|
||||
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.MetricFamily;
|
||||
import org.apache.hertzbeat.collector.collect.prometheus.parser.OnlineParser;
|
||||
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.dispatch. DispatchConstants;
|
||||
import org. apache.hertzbeat.collector.util.CollectUtil;
|
||||
import org.apache.hertzbeat.collector.util.JsonPathParser;
|
||||
import org.apache.hertzbeat.collector.util.TimeExpressionUtil;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.constants.NetworkConstants;
|
||||
import org.apache.hertzbeat.common.constants.SignConstants;
|
||||
import org.apache.hertzbeat.common.entity.job.Metrics;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.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.constants. NetworkConstants;
|
||||
import org.apache. hertzbeat.common.constants. SignConstants;
|
||||
import org.apache.hertzbeat.common.entity.job. Metrics;
|
||||
import org.apache. hertzbeat.common.entity. job.protocol.HttpProtocol;
|
||||
import org.apache.hertzbeat. common.entity.message.CollectRep;
|
||||
import org.apache.hertzbeat. common.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.HttpEntity;
|
||||
import org.apache.http.HttpHeaders;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.apache.http.auth.AuthScope;
|
||||
import org.apache.http.auth.UsernamePasswordCredentials;
|
||||
import org.apache.http.client.ClientProtocolException;
|
||||
import org.apache.http.client.CredentialsProvider;
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
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.client.BasicCredentialsProvider;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.apache. hc.core5.http.ClassicHttpResponse;
|
||||
import org. apache.hc.core5. http.Header;
|
||||
import org.apache.hc.core5.http.HttpEntity;
|
||||
import org.apache. hc.core5.http. HttpHeaders;
|
||||
import org.apache.hc.core5.http.HttpStatus;
|
||||
import org.apache. hc.client5.http.auth.AuthScope;
|
||||
import org.apache. hc.client5.http. auth.UsernamePasswordCredentials;
|
||||
import org.apache.hc.client5.http.ClientProtocolException;
|
||||
import org. apache.hc.client5. http.config.RequestConfig;
|
||||
import org. apache.hc.client5. http.classic.methods.HttpGet;
|
||||
import org. apache.hc.core5.http.io.HttpClientResponseHandler;
|
||||
import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
|
||||
import org. apache.hc.client5.http.protocol.HttpClientContext;
|
||||
import org.apache.hc.core5.http.io.entity.StringEntity;
|
||||
import org.apache. hc.client5.http.impl.auth.BasicCredentialsProvider;
|
||||
import org.apache.hc. core5.http.protocol.HttpContext;
|
||||
import org. apache.hc.core5. http.io.entity.EntityUtils;
|
||||
import org.apache. hc.core5.util.Timeout;
|
||||
import org. springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org. springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.util.UriUtils;
|
||||
import org.xml.sax.InputSource;
|
||||
@@ -97,9 +97,9 @@ 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.regex.Pattern;
|
||||
import java.util.HashSet;
|
||||
import java.util.ArrayList;
|
||||
import java.util. ArrayList;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
@@ -109,18 +109,24 @@ import java.util.Collections;
|
||||
public class HttpCollectImpl extends AbstractCollect {
|
||||
|
||||
/**
|
||||
* Pre-compiled regex patterns for dangerous Xpath detection.
|
||||
* Compiled once at class load for performance.
|
||||
* Inner class to wrap HTTP request with its configuration
|
||||
*/
|
||||
private static final List<Pattern> DANGEROUS_XPATH_PATTERNS;
|
||||
private static class HttpRequestWithConfig {
|
||||
private final ClassicHttpRequest request;
|
||||
private final RequestConfig requestConfig;
|
||||
|
||||
static {
|
||||
List<Pattern> patterns = new ArrayList<>();
|
||||
for (String pattern : CollectorConstants.DANGEROUS_XPATH_PATTERNS) {
|
||||
// Use CASE_INSENSITIVE to prevent bypass via case variations (e.g., //TEXT() vs //text())
|
||||
patterns.add(Pattern.compile(pattern, Pattern.CASE_INSENSITIVE | Pattern.DOTALL));
|
||||
public HttpRequestWithConfig(ClassicHttpRequest request, RequestConfig requestConfig) {
|
||||
this.request = request;
|
||||
this.requestConfig = requestConfig;
|
||||
}
|
||||
|
||||
public ClassicHttpRequest getRequest() {
|
||||
return request;
|
||||
}
|
||||
|
||||
public RequestConfig getRequestConfig() {
|
||||
return requestConfig;
|
||||
}
|
||||
DANGEROUS_XPATH_PATTERNS = Collections.unmodifiableList(patterns);
|
||||
}
|
||||
|
||||
private final Set<Integer> defaultSuccessStatusCodes = Set.of(
|
||||
@@ -139,28 +145,37 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void collect(CollectRep.MetricsData.Builder builder, Metrics metrics) {
|
||||
public void collect(CollectRep. MetricsData.Builder builder, Metrics metrics) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
HttpProtocol httpProtocol = metrics.getHttp();
|
||||
String url = httpProtocol.getUrl();
|
||||
if (!StringUtils.hasText(url) || !url.startsWith(RIGHT_DASH)) {
|
||||
httpProtocol.setUrl(StringUtils.hasText(url) ? RIGHT_DASH + url.trim() : RIGHT_DASH);
|
||||
if (! StringUtils.hasText(url) || !url.startsWith(RIGHT_DASH)) {
|
||||
httpProtocol.setUrl(StringUtils.hasText(url) ? RIGHT_DASH + url.trim() : RIGHT_DASH);
|
||||
}
|
||||
if (CollectionUtils.isEmpty(httpProtocol.getSuccessCodes())) {
|
||||
httpProtocol.setSuccessCodes(List.of(HttpStatus.SC_OK + ""));
|
||||
httpProtocol. setSuccessCodes(List. of(HttpStatus.SC_OK + ""));
|
||||
}
|
||||
|
||||
HttpContext httpContext = createHttpContext(metrics.getHttp());
|
||||
HttpUriRequest request = createHttpRequest(metrics.getHttp());
|
||||
try (CloseableHttpResponse response = CommonHttpClient.getHttpClient().execute(request, httpContext)) {
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
HttpRequestWithConfig requestWithConfig = createHttpRequest(metrics.getHttp());
|
||||
if (requestWithConfig == null) {
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg("Failed to create HTTP request");
|
||||
return;
|
||||
}
|
||||
|
||||
ClassicHttpRequest request = requestWithConfig. getRequest();
|
||||
HttpContext httpContext = createHttpContext(metrics. getHttp(), requestWithConfig. getRequestConfig());
|
||||
|
||||
// Use HttpClientResponseHandler to handle the response and avoid deprecated execute method
|
||||
HttpClientResponseHandler<Void> responseHandler = response -> {
|
||||
int statusCode = response.getCode();
|
||||
boolean isSuccessInvoke = checkSuccessInvoke(metrics, statusCode);
|
||||
log.debug("http response status: {}", statusCode);
|
||||
if (!isSuccessInvoke) {
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setCode(CollectRep.Code. FAIL);
|
||||
builder.setMsg(NetworkConstants.STATUS_CODE + SignConstants.BLANK + statusCode);
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
long responseTime = System.currentTimeMillis() - startTime;
|
||||
@@ -170,25 +185,20 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
try {
|
||||
if (DispatchConstants.PARSE_PROMETHEUS.equals(parseType)) {
|
||||
if (entity != null) {
|
||||
parseResponseByPrometheusExporter(entity.getContent(), metrics.getAliasFields(), builder);
|
||||
parseResponseByPrometheusExporter(entity. getContent(), metrics.getAliasFields(), builder);
|
||||
}
|
||||
} else if (DispatchConstants.PARSE_HEADER.equals(parseType)) {
|
||||
parseResponseByHeader(builder, metrics.getAliasFields(), response);
|
||||
// Consume entity to release connection
|
||||
EntityUtils.consumeQuietly(entity);
|
||||
} else {
|
||||
/*
|
||||
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 = entity == null ? "" : EntityUtils.toString(entity, StandardCharsets.UTF_8);
|
||||
String resp = entity == null ? "" : EntityUtils. toString(entity, StandardCharsets. UTF_8);
|
||||
if (!StringUtils.hasText(resp)) {
|
||||
log.info("http response entity is empty, status: {}.", statusCode);
|
||||
}
|
||||
switch (parseType) {
|
||||
case DispatchConstants.PARSE_JSON_PATH ->
|
||||
parseResponseByJsonPath(resp, metrics.getAliasFields(), metrics.getHttp(), builder, responseTime);
|
||||
parseResponseByJsonPath(resp, metrics. getAliasFields(), metrics. getHttp(), builder, responseTime);
|
||||
case DispatchConstants.PARSE_PROM_QL ->
|
||||
parseResponseByPromQl(resp, metrics.getAliasFields(), metrics.getHttp(), builder);
|
||||
case DispatchConstants.PARSE_XML_PATH ->
|
||||
@@ -196,7 +206,7 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
case DispatchConstants.PARSE_WEBSITE ->
|
||||
parseResponseByWebsite(resp, metrics, metrics.getHttp(), builder, responseTime, statusCode);
|
||||
case DispatchConstants.PARSE_SITE_MAP ->
|
||||
parseResponseBySiteMap(resp, metrics.getAliasFields(), builder);
|
||||
parseResponseBySiteMap(resp, metrics. getAliasFields(), builder);
|
||||
case DispatchConstants.PARSE_CONFIG ->
|
||||
parseResponseByConfig(resp, metrics.getAliasFields(), metrics.getHttp(), builder, responseTime);
|
||||
default ->
|
||||
@@ -208,15 +218,20 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg("parse response data error:" + e.getMessage());
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
try {
|
||||
CommonHttpClient.getHttpClient().execute(request, httpContext, responseHandler);
|
||||
} catch (ClientProtocolException e1) {
|
||||
String errorMsg = CommonUtil.getMessageFromThrowable(e1);
|
||||
log.error(errorMsg);
|
||||
builder.setCode(CollectRep.Code.UN_CONNECTABLE);
|
||||
builder. setCode(CollectRep. Code.UN_CONNECTABLE);
|
||||
builder.setMsg(errorMsg);
|
||||
} catch (UnknownHostException e2) {
|
||||
String errorMsg = CommonUtil.getMessageFromThrowable(e2);
|
||||
log.info(errorMsg);
|
||||
builder.setCode(CollectRep.Code.UN_REACHABLE);
|
||||
builder. setCode(CollectRep. Code.UN_REACHABLE);
|
||||
builder.setMsg("unknown host:" + errorMsg);
|
||||
} catch (InterruptedIOException | ConnectException | SSLException e3) {
|
||||
String errorMsg = CommonUtil.getMessageFromThrowable(e3);
|
||||
@@ -225,7 +240,7 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
builder.setMsg(errorMsg);
|
||||
} catch (IOException e4) {
|
||||
String errorMsg = CommonUtil.getMessageFromThrowable(e4);
|
||||
log.info(errorMsg);
|
||||
log. info(errorMsg);
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg(errorMsg);
|
||||
} catch (Exception e) {
|
||||
@@ -233,17 +248,13 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
log.error(errorMsg, e);
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg(errorMsg);
|
||||
} finally {
|
||||
if (request != null) {
|
||||
request.abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void parseResponseByHeader(CollectRep.MetricsData.Builder builder, List<String> aliases, CloseableHttpResponse response) {
|
||||
CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
|
||||
private void parseResponseByHeader(CollectRep.MetricsData.Builder builder, List<String> aliases, ClassicHttpResponse response) {
|
||||
CollectRep.ValueRow. Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
|
||||
for (String alias : aliases) {
|
||||
if (!StringUtils.hasText(alias)) {
|
||||
if (! StringUtils.hasText(alias)) {
|
||||
valueRowBuilder.addColumn(CommonConstants.NULL_VALUE);
|
||||
continue;
|
||||
}
|
||||
@@ -255,7 +266,7 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
|
||||
valueRowBuilder.addColumn(firstHeader.getValue());
|
||||
}
|
||||
builder.addValueRow(valueRowBuilder.build());
|
||||
builder. addValueRow(valueRowBuilder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -280,7 +291,7 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
private void addColumnForSummary(Long responseTime, CollectRep.ValueRow.Builder valueRowBuilder, int keywordNum, String alias) {
|
||||
if (NetworkConstants.RESPONSE_TIME.equalsIgnoreCase(alias)) {
|
||||
valueRowBuilder.addColumn(responseTime.toString());
|
||||
} else if (CollectorConstants.KEYWORD.equalsIgnoreCase(alias)) {
|
||||
} else if (CollectorConstants.KEYWORD. equalsIgnoreCase(alias)) {
|
||||
valueRowBuilder.addColumn(Integer.toString(keywordNum));
|
||||
} else {
|
||||
valueRowBuilder.addColumn(CommonConstants.NULL_VALUE);
|
||||
@@ -299,13 +310,13 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
DocumentBuilder db = dbf.newDocumentBuilder();
|
||||
Document document = db.parse(new InputSource(new StringReader(resp)));
|
||||
NodeList urlList = document.getElementsByTagName("url");
|
||||
for (int i = 0; i < urlList.getLength(); i++) {
|
||||
for (int i = 0; i < urlList. getLength(); i++) {
|
||||
Node urlNode = urlList.item(i);
|
||||
NodeList childNodes = urlNode.getChildNodes();
|
||||
for (int k = 0; k < childNodes.getLength(); k++) {
|
||||
Node currentNode = childNodes.item(k);
|
||||
// distinguish between text nodes and element nodes
|
||||
if (currentNode.getNodeType() == Node.ELEMENT_NODE && "loc".equals(currentNode.getNodeName())) {
|
||||
if (currentNode.getNodeType() == Node. ELEMENT_NODE && "loc".equals(currentNode.getNodeName())) {
|
||||
// retrieves the value of the loc node
|
||||
siteUrls.add(currentNode.getFirstChild().getNodeValue());
|
||||
break;
|
||||
@@ -335,10 +346,13 @@ 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();
|
||||
|
||||
HttpClientResponseHandler<Integer> statusHandler = response -> {
|
||||
EntityUtils.consume(response.getEntity());
|
||||
}
|
||||
return response.getCode();
|
||||
};
|
||||
statusCode = CommonHttpClient.getHttpClient().execute(httpGet, statusHandler);
|
||||
|
||||
} catch (ClientProtocolException e1) {
|
||||
if (e1.getCause() != null) {
|
||||
errorMsg = e1.getCause().getMessage();
|
||||
@@ -362,7 +376,7 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
} else if (NetworkConstants.STATUS_CODE.equalsIgnoreCase(alias)) {
|
||||
valueRowBuilder.addColumn(statusCode == null
|
||||
? CommonConstants.NULL_VALUE : String.valueOf(statusCode));
|
||||
} else if (NetworkConstants.RESPONSE_TIME.equalsIgnoreCase(alias)) {
|
||||
} else if (NetworkConstants. RESPONSE_TIME.equalsIgnoreCase(alias)) {
|
||||
valueRowBuilder.addColumn(String.valueOf(responseTime));
|
||||
} else if (NetworkConstants.ERROR_MSG.equalsIgnoreCase(alias)) {
|
||||
valueRowBuilder.addColumn(errorMsg);
|
||||
@@ -374,99 +388,37 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the Xpath expression to prevent DoS attacks.
|
||||
* Checks for dangerous patterns that could traverse the entire XML document.
|
||||
* Uses pre-compiled patterns for performance and case-insensitive matching for security.
|
||||
*
|
||||
* @param xpathExpression the Xpath expression to validate
|
||||
* @throws IllegalArgumentException if the expression contains dangerous patterns
|
||||
*/
|
||||
private void validateXpathExpression(String xpathExpression) throws IllegalArgumentException {
|
||||
if (!StringUtils.hasText(xpathExpression)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check against dangerous patterns using pre-compiled regex
|
||||
for (Pattern pattern : DANGEROUS_XPATH_PATTERNS) {
|
||||
Matcher matcher = pattern.matcher(xpathExpression);
|
||||
if (matcher.find()) {
|
||||
throw new IllegalArgumentException(
|
||||
"Xpath expression contains dangerous pattern that may cause DoS: " + pattern.pattern()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for excessive wildcard usage (more than 3 // or * operators)
|
||||
long descendantAxisCount = xpathExpression.chars().filter(ch -> ch == '/').count();
|
||||
long wildcardCount = xpathExpression.chars().filter(ch -> ch == '*').count();
|
||||
|
||||
if (descendantAxisCount > 10 || wildcardCount > 5) {
|
||||
throw new IllegalArgumentException(
|
||||
"Xpath expression contains too many wildcards or descendant axes, potential DoS risk"
|
||||
);
|
||||
}
|
||||
|
||||
log.debug("Xpath expression validation passed: {}", xpathExpression);
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
// Layer 1: Validate Xpath expression is not empty
|
||||
if (!StringUtils.hasText(xpathExpression)) {
|
||||
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");
|
||||
builder.setMsg("XPath expression is empty");
|
||||
return;
|
||||
}
|
||||
|
||||
// Layer 2: Check XML response size to prevent memory exhaustion
|
||||
if (resp != null && resp.length() > CollectorConstants.MAX_XML_RESPONSE_SIZE) {
|
||||
log.warn("XML response size {} bytes exceeds maximum allowed size {} bytes",
|
||||
resp.length(), CollectorConstants.MAX_XML_RESPONSE_SIZE);
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg("XML response exceeds maximum allowed size of "
|
||||
+ (CollectorConstants.MAX_XML_RESPONSE_SIZE / 1024 / 1024) + "MB");
|
||||
return;
|
||||
}
|
||||
|
||||
// Layer 3: Validate Xpath expression for dangerous patterns
|
||||
try {
|
||||
validateXpathExpression(xpathExpression);
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.warn("Xpath expression validation failed: {}", e.getMessage());
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg(e.getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
int keywordNum = CollectUtil.countMatchKeyword(resp, http.getKeyword());
|
||||
int keywordNum = CollectUtil. countMatchKeyword(resp, http.getKeyword());
|
||||
|
||||
try {
|
||||
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
||||
|
||||
// Layer 4: Enable XML secure processing and XXE protection
|
||||
dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
|
||||
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();
|
||||
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);
|
||||
NodeList nodeList = (NodeList) xpath. evaluate(xpathExpression, document, XPathConstants.NODESET);
|
||||
|
||||
if (nodeList == null || nodeList.getLength() == 0) {
|
||||
log.debug("Xpath expression '{}' returned no nodes.", xpathExpression);
|
||||
log.debug("XPath expression '{}' returned no nodes.", xpathExpression);
|
||||
boolean requestedSummaryFields = aliasFields.stream()
|
||||
.anyMatch(alias -> NetworkConstants.RESPONSE_TIME.equalsIgnoreCase(alias)
|
||||
|| CollectorConstants.KEYWORD.equalsIgnoreCase(alias));
|
||||
@@ -476,10 +428,10 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
for (String alias : aliasFields) {
|
||||
if (NetworkConstants.RESPONSE_TIME.equalsIgnoreCase(alias)) {
|
||||
valueRowBuilder.addColumn(responseTime.toString());
|
||||
} else if (CollectorConstants.KEYWORD.equalsIgnoreCase(alias)) {
|
||||
} else if (CollectorConstants. KEYWORD.equalsIgnoreCase(alias)) {
|
||||
valueRowBuilder.addColumn(Integer.toString(keywordNum));
|
||||
} else {
|
||||
valueRowBuilder.addColumn(CommonConstants.NULL_VALUE);
|
||||
valueRowBuilder.addColumn(CommonConstants. NULL_VALUE);
|
||||
}
|
||||
}
|
||||
builder.addValueRow(valueRowBuilder.build());
|
||||
@@ -487,30 +439,21 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
return;
|
||||
}
|
||||
|
||||
// Layer 5: Limit the number of results to prevent excessive resource consumption
|
||||
int resultSize = nodeList.getLength();
|
||||
int maxResults = CollectorConstants.MAX_XPATH_RESULT_NODES;
|
||||
if (resultSize > maxResults) {
|
||||
log.warn("Xpath expression returned {} nodes, exceeding limit of {}. Processing first {} nodes only.",
|
||||
resultSize, maxResults, maxResults);
|
||||
resultSize = maxResults;
|
||||
}
|
||||
|
||||
for (int i = 0; i < resultSize; i++) {
|
||||
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)) {
|
||||
if (NetworkConstants. RESPONSE_TIME.equalsIgnoreCase(alias)) {
|
||||
valueRowBuilder.addColumn(responseTime.toString());
|
||||
} else if (CollectorConstants.KEYWORD.equalsIgnoreCase(alias)) {
|
||||
} 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);
|
||||
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());
|
||||
log.warn("Failed to evaluate XPath '{}' for node [{}]: {}", alias, node.getNodeName(), e.getMessage());
|
||||
valueRowBuilder.addColumn(CommonConstants.NULL_VALUE);
|
||||
}
|
||||
}
|
||||
@@ -519,8 +462,8 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to parse XML response with Xpath '{}': {}", xpathExpression, e.getMessage(), e);
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -529,25 +472,12 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
|
||||
/**
|
||||
* 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)) {
|
||||
if (! StringUtils.hasText(resp)) {
|
||||
log.warn("Http collect parse type is config, but response body is empty.");
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder. setCode(CollectRep. Code.FAIL);
|
||||
builder.setMsg("Response body is empty");
|
||||
return;
|
||||
}
|
||||
@@ -557,14 +487,14 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
properties.load(reader);
|
||||
} catch (IOException e) {
|
||||
log.warn("Failed to parse config response: {}", e.getMessage(), e);
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
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());
|
||||
int keywordNum = CollectUtil. countMatchKeyword(resp, http.getKeyword());
|
||||
|
||||
if (!StringUtils.hasText(arrayBasePath)) {
|
||||
if (! StringUtils.hasText(arrayBasePath)) {
|
||||
CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
|
||||
for (String alias : aliasFields) {
|
||||
if (NetworkConstants.RESPONSE_TIME.equalsIgnoreCase(alias)) {
|
||||
@@ -573,7 +503,7 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
valueRowBuilder.addColumn(Integer.toString(keywordNum));
|
||||
} else {
|
||||
String value = properties.getProperty(alias);
|
||||
valueRowBuilder.addColumn(value != null ? value : CommonConstants.NULL_VALUE);
|
||||
valueRowBuilder. addColumn(value != null ? value : CommonConstants. NULL_VALUE);
|
||||
}
|
||||
}
|
||||
CollectRep.ValueRow valueRow = valueRowBuilder.build();
|
||||
@@ -606,13 +536,13 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
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));
|
||||
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);
|
||||
valueRowBuilder. addColumn(value != null ? value : CommonConstants.NULL_VALUE);
|
||||
}
|
||||
}
|
||||
CollectRep.ValueRow valueRow = valueRowBuilder.build();
|
||||
@@ -637,24 +567,24 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
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))) {
|
||||
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)) {
|
||||
if ((NetworkConstants.RESPONSE_TIME. equalsIgnoreCase(alias) || CollectorConstants.KEYWORD. equalsIgnoreCase(alias)) && ! CommonConstants.NULL_VALUE.equals(columnValue)) {
|
||||
hasMeaningfulData = true;
|
||||
}
|
||||
}
|
||||
return hasMeaningfulData;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void parseResponseByJsonPath(String resp, List<String> aliasFields, HttpProtocol http,
|
||||
CollectRep.MetricsData.Builder builder, Long responseTime) {
|
||||
List<Object> results = JsonPathParser.parseContentWithJsonPath(resp, http.getParseScript());
|
||||
int keywordNum = CollectUtil.countMatchKeyword(resp, http.getKeyword());
|
||||
int keywordNum = CollectUtil. countMatchKeyword(resp, http.getKeyword());
|
||||
for (int i = 0; i < results.size(); i++) {
|
||||
Object objectValue = results.get(i);
|
||||
// if a property is missing or empty due to target version issues, filter it. Refer to the app-elasticsearch.yml configuration under name: nodes
|
||||
if (objectValue == null) {
|
||||
continue;
|
||||
}
|
||||
@@ -666,8 +596,8 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
if (value != null) {
|
||||
valueRowBuilder.addColumn(String.valueOf(value));
|
||||
} else {
|
||||
if (alias.startsWith("$.")) {
|
||||
List<Object> subResults = JsonPathParser.parseContentWithJsonPath(resp, http.getParseScript() + alias.substring(1));
|
||||
if (alias.startsWith("$. ")) {
|
||||
List<Object> subResults = JsonPathParser.parseContentWithJsonPath(resp, http.getParseScript() + alias. substring(1));
|
||||
if (subResults != null && subResults.size() > i) {
|
||||
Object resultValue = subResults.get(i);
|
||||
valueRowBuilder.addColumn(resultValue == null ? CommonConstants.NULL_VALUE : String.valueOf(resultValue));
|
||||
@@ -721,7 +651,7 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
return;
|
||||
}
|
||||
MetricFamily metricFamily = metricFamilyMap.get(metrics);
|
||||
if (null == metricFamily || CollectionUtils.isEmpty(metricFamily.getMetricList())) {
|
||||
if (null == metricFamily || CollectionUtils.isEmpty(metricFamily. getMetricList())) {
|
||||
return;
|
||||
}
|
||||
for (MetricFamily.Metric metric : metricFamily.getMetricList()) {
|
||||
@@ -733,7 +663,7 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
String columnValue = labelMap.get(aliasField);
|
||||
if (columnValue != null) {
|
||||
valueRowBuilder.addColumn(columnValue);
|
||||
} else if (CommonConstants.PROM_VALUE.equals(aliasField) || CommonConstants.PROM_METRIC_VALUE.equals(aliasField)) {
|
||||
} else if (CommonConstants. PROM_VALUE.equals(aliasField) || CommonConstants.PROM_METRIC_VALUE.equals(aliasField)) {
|
||||
valueRowBuilder.addColumn(String.valueOf(metric.getValue()));
|
||||
} else {
|
||||
valueRowBuilder.addColumn(CommonConstants.NULL_VALUE);
|
||||
@@ -745,9 +675,9 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
|
||||
|
||||
private void parseResponseByDefault(String resp, List<String> aliasFields, HttpProtocol http,
|
||||
CollectRep.MetricsData.Builder builder, Long responseTime) {
|
||||
CollectRep.MetricsData. Builder builder, Long responseTime) {
|
||||
JsonElement element = JsonParser.parseString(resp);
|
||||
int keywordNum = CollectUtil.countMatchKeyword(resp, http.getKeyword());
|
||||
int keywordNum = CollectUtil. countMatchKeyword(resp, http.getKeyword());
|
||||
if (element.isJsonArray()) {
|
||||
JsonArray array = element.getAsJsonArray();
|
||||
for (JsonElement jsonElement : array) {
|
||||
@@ -779,46 +709,53 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
* create httpContext
|
||||
*
|
||||
* @param httpProtocol http protocol
|
||||
* @param requestConfig request config (can be null)
|
||||
* @return context
|
||||
*/
|
||||
public HttpContext createHttpContext(HttpProtocol httpProtocol) {
|
||||
HttpProtocol.Authorization auth = httpProtocol.getAuthorization();
|
||||
public HttpContext createHttpContext(HttpProtocol httpProtocol, RequestConfig requestConfig) {
|
||||
HttpClientContext clientContext = HttpClientContext.create();
|
||||
|
||||
// Set request config if provided
|
||||
if (requestConfig != null) {
|
||||
clientContext.setRequestConfig(requestConfig);
|
||||
}
|
||||
|
||||
HttpProtocol. Authorization auth = httpProtocol.getAuthorization();
|
||||
if (auth != null && DispatchConstants.DIGEST_AUTH.equals(auth.getType())) {
|
||||
HttpClientContext clientContext = new HttpClientContext();
|
||||
if (StringUtils.hasText(auth.getDigestAuthUsername())
|
||||
&& StringUtils.hasText(auth.getDigestAuthPassword())) {
|
||||
CredentialsProvider provider = new BasicCredentialsProvider();
|
||||
BasicCredentialsProvider provider = new BasicCredentialsProvider();
|
||||
UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(auth.getDigestAuthUsername(),
|
||||
auth.getDigestAuthPassword());
|
||||
auth.getDigestAuthPassword(). toCharArray());
|
||||
AuthScope authScope = new AuthScope(httpProtocol.getHost(), Integer.parseInt(httpProtocol.getPort()));
|
||||
provider.setCredentials(authScope, credentials);
|
||||
|
||||
clientContext.setCredentialsProvider(provider);
|
||||
return clientContext;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
return clientContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* create http request
|
||||
*
|
||||
* @param httpProtocol http params
|
||||
* @return http uri request
|
||||
* @return http request with config
|
||||
*/
|
||||
public HttpUriRequest createHttpRequest(HttpProtocol httpProtocol) {
|
||||
RequestBuilder requestBuilder;
|
||||
String httpMethod = httpProtocol.getMethod().toUpperCase();
|
||||
private HttpRequestWithConfig createHttpRequest(HttpProtocol httpProtocol) {
|
||||
ClassicRequestBuilder requestBuilder;
|
||||
String httpMethod = httpProtocol.getMethod(). toUpperCase();
|
||||
if (HttpMethod.GET.matches(httpMethod)) {
|
||||
requestBuilder = RequestBuilder.get();
|
||||
requestBuilder = ClassicRequestBuilder.get();
|
||||
} else if (HttpMethod.POST.matches(httpMethod)) {
|
||||
requestBuilder = RequestBuilder.post();
|
||||
requestBuilder = ClassicRequestBuilder.post();
|
||||
} else if (HttpMethod.PUT.matches(httpMethod)) {
|
||||
requestBuilder = RequestBuilder.put();
|
||||
requestBuilder = ClassicRequestBuilder.put();
|
||||
} else if (HttpMethod.DELETE.matches(httpMethod)) {
|
||||
requestBuilder = RequestBuilder.delete();
|
||||
} else if (HttpMethod.PATCH.matches(httpMethod)) {
|
||||
requestBuilder = RequestBuilder.patch();
|
||||
requestBuilder = ClassicRequestBuilder.delete();
|
||||
} else if (HttpMethod. PATCH.matches(httpMethod)) {
|
||||
requestBuilder = ClassicRequestBuilder.patch();
|
||||
} else {
|
||||
// not support the method
|
||||
log.error("not support the http method: {}.", httpProtocol.getMethod());
|
||||
@@ -829,17 +766,17 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
boolean enableUrlEncoding = Boolean.parseBoolean(httpProtocol.getEnableUrlEncoding());
|
||||
StringBuilder queryParams = new StringBuilder();
|
||||
|
||||
if (params != null && !params.isEmpty()) {
|
||||
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)) {
|
||||
if (! StringUtils.hasText(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!queryParams.isEmpty()) {
|
||||
queryParams.append("&");
|
||||
if (! queryParams.isEmpty()) {
|
||||
queryParams. append("&");
|
||||
}
|
||||
|
||||
if (enableUrlEncoding) {
|
||||
@@ -850,9 +787,9 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
if (StringUtils.hasText(value)) {
|
||||
String calculatedValue = TimeExpressionUtil.calculate(value);
|
||||
if (enableUrlEncoding) {
|
||||
calculatedValue = UriUtils.encodeQueryParam(calculatedValue, "UTF-8");
|
||||
calculatedValue = UriUtils. encodeQueryParam(calculatedValue, "UTF-8");
|
||||
}
|
||||
queryParams.append("=").append(calculatedValue);
|
||||
queryParams. append("=").append(calculatedValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -863,7 +800,7 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
requestBuilder.addHeader(HttpHeaders.USER_AGENT, NetworkConstants.USER_AGENT);
|
||||
// headers The custom request header is overwritten here
|
||||
Map<String, String> headers = httpProtocol.getHeaders();
|
||||
if (headers != null && !headers.isEmpty()) {
|
||||
if (headers != null && ! headers.isEmpty()) {
|
||||
for (Map.Entry<String, String> header : headers.entrySet()) {
|
||||
if (StringUtils.hasText(header.getValue())) {
|
||||
requestBuilder.addHeader(header.getKey(), header.getValue());
|
||||
@@ -871,7 +808,7 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
}
|
||||
}
|
||||
// add accept
|
||||
if (DispatchConstants.PARSE_DEFAULT.equals(httpProtocol.getParseType())
|
||||
if (DispatchConstants.PARSE_DEFAULT. equals(httpProtocol.getParseType())
|
||||
|| DispatchConstants.PARSE_JSON_PATH.equals(httpProtocol.getParseType())) {
|
||||
requestBuilder.addHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
|
||||
} else if (DispatchConstants.PARSE_XML_PATH.equals(httpProtocol.getParseType())) {
|
||||
@@ -887,10 +824,10 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
requestBuilder.addHeader(HttpHeaders.AUTHORIZATION, value);
|
||||
} else if (DispatchConstants.BASIC_AUTH.equals(authorization.getType())) {
|
||||
if (StringUtils.hasText(authorization.getBasicAuthUsername())
|
||||
&& StringUtils.hasText(authorization.getBasicAuthPassword())) {
|
||||
&& StringUtils.hasText(authorization. getBasicAuthPassword())) {
|
||||
String authStr = authorization.getBasicAuthUsername() + SignConstants.DOUBLE_MARK + authorization.getBasicAuthPassword();
|
||||
String encodedAuth = Base64Util.encode(authStr);
|
||||
requestBuilder.addHeader(HttpHeaders.AUTHORIZATION, DispatchConstants.BASIC + SignConstants.BLANK + encodedAuth);
|
||||
requestBuilder.addHeader(HttpHeaders. AUTHORIZATION, DispatchConstants.BASIC + SignConstants.BLANK + encodedAuth);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -905,14 +842,14 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
if (enableUrlEncoding) {
|
||||
// if the url contains parameters directly
|
||||
if (httpProtocol.getUrl().contains("?")) {
|
||||
String path = httpProtocol.getUrl().substring(0, httpProtocol.getUrl().indexOf("?"));
|
||||
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();
|
||||
uri = httpProtocol. getUrl();
|
||||
}
|
||||
|
||||
// append query params
|
||||
@@ -922,10 +859,10 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
|
||||
String finalUri;
|
||||
if (IpDomainUtil.isHasSchema(httpProtocol.getHost())) {
|
||||
finalUri = httpProtocol.getHost() + ":" + httpProtocol.getPort() + uri;
|
||||
finalUri = httpProtocol.getHost() + ":" + httpProtocol. getPort() + uri;
|
||||
} else {
|
||||
String ipAddressType = IpDomainUtil.checkIpAddressType(httpProtocol.getHost());
|
||||
String baseUri = NetworkConstants.IPV6.equals(ipAddressType)
|
||||
String ipAddressType = IpDomainUtil. checkIpAddressType(httpProtocol.getHost());
|
||||
String baseUri = NetworkConstants.IPV6. equals(ipAddressType)
|
||||
? String.format("[%s]:%s%s", httpProtocol.getHost(), httpProtocol.getPort(), uri)
|
||||
: String.format("%s:%s%s", httpProtocol.getHost(), httpProtocol.getPort(), uri);
|
||||
boolean ssl = Boolean.parseBoolean(httpProtocol.getSsl());
|
||||
@@ -943,21 +880,23 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
throw e;
|
||||
}
|
||||
|
||||
// custom timeout
|
||||
// custom timeout - create RequestConfig separately
|
||||
RequestConfig requestConfig = null;
|
||||
int timeout = CollectUtil.getTimeout(httpProtocol.getTimeout(), 0);
|
||||
if (timeout > 0) {
|
||||
RequestConfig requestConfig = RequestConfig.custom()
|
||||
.setConnectTimeout(timeout)
|
||||
.setSocketTimeout(timeout)
|
||||
requestConfig = RequestConfig.custom()
|
||||
.setConnectTimeout(Timeout.ofMilliseconds(timeout))
|
||||
.setResponseTimeout(Timeout.ofMilliseconds(timeout))
|
||||
.setRedirectsEnabled(true)
|
||||
.build();
|
||||
requestBuilder.setConfig(requestConfig);
|
||||
}
|
||||
return requestBuilder.build();
|
||||
|
||||
// build the request and return with config
|
||||
return new HttpRequestWithConfig(requestBuilder. build(), requestConfig);
|
||||
}
|
||||
|
||||
private boolean checkSuccessInvoke(Metrics metrics, int statusCode) {
|
||||
List<String> successCodes = metrics.getHttp().getSuccessCodes();
|
||||
List<String> successCodes = metrics.getHttp(). getSuccessCodes();
|
||||
Set<Integer> successCodeSet = successCodes != null ? successCodes.stream().map(code -> {
|
||||
try {
|
||||
return Integer.valueOf(code);
|
||||
@@ -970,4 +909,4 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
}
|
||||
return successCodeSet.contains(statusCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -117,11 +117,11 @@ public class SslCertificateCollectImpl extends AbstractCollect {
|
||||
} else if (NAME_EXPIRED.equalsIgnoreCase(alias)) {
|
||||
valueRowBuilder.addColumn(Boolean.toString(expired));
|
||||
} else if (NAME_START_TIME.equalsIgnoreCase(alias)) {
|
||||
valueRowBuilder.addColumn(x509Certificate.getNotBefore().toLocaleString());
|
||||
valueRowBuilder.addColumn(x509Certificate.getNotBefore().toString());
|
||||
} else if (NAME_START_TIMESTAMP.equalsIgnoreCase(alias)) {
|
||||
valueRowBuilder.addColumn(String.valueOf(x509Certificate.getNotBefore().getTime()));
|
||||
} else if (NAME_END_TIME.equalsIgnoreCase(alias)) {
|
||||
valueRowBuilder.addColumn(x509Certificate.getNotAfter().toLocaleString());
|
||||
valueRowBuilder.addColumn(x509Certificate.getNotAfter().toString());
|
||||
} else if (NAME_END_TIMESTAMP.equalsIgnoreCase(alias)) {
|
||||
valueRowBuilder.addColumn(String.valueOf(x509Certificate.getNotAfter().getTime()));
|
||||
} else {
|
||||
|
||||
+61
-34
@@ -6,7 +6,7 @@
|
||||
* (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
|
||||
* 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,
|
||||
@@ -44,16 +44,17 @@ import org.apache.hertzbeat.common.entity.job.protocol.NginxProtocol;
|
||||
import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
import org.apache.hertzbeat.common.util.CommonUtil;
|
||||
import org.apache.hertzbeat.common.util.IpDomainUtil;
|
||||
import org.apache.http.HttpHeaders;
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpUriRequest;
|
||||
import org.apache.http.client.methods.RequestBuilder;
|
||||
import org.apache.http.client.protocol.HttpClientContext;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.apache.hc.core5.http.HttpHeaders;
|
||||
import org.apache.hc.core5.http.HttpHost;
|
||||
import org.apache.hc.core5.http.HttpStatus;
|
||||
import org.apache.hc.core5.http.ClassicHttpRequest;
|
||||
import org.apache.hc.core5.http.io.HttpClientResponseHandler;
|
||||
import org.apache.hc.client5.http.config.RequestConfig;
|
||||
import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
|
||||
import org.apache.hc.client5.http.protocol.HttpClientContext;
|
||||
import org.apache.hc.core5.http.protocol.HttpContext;
|
||||
import org.apache.hc.core5.http.io.entity.EntityUtils;
|
||||
import org.apache.hc.core5.util.Timeout;
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
/**
|
||||
@@ -62,6 +63,27 @@ import org.springframework.http.MediaType;
|
||||
@Slf4j
|
||||
public class NginxCollectImpl extends AbstractCollect {
|
||||
|
||||
/**
|
||||
* Inner class to wrap HTTP request with its configuration
|
||||
*/
|
||||
private static class HttpRequestWithConfig {
|
||||
private final ClassicHttpRequest request;
|
||||
private final RequestConfig requestConfig;
|
||||
|
||||
public HttpRequestWithConfig(ClassicHttpRequest request, RequestConfig requestConfig) {
|
||||
this.request = request;
|
||||
this.requestConfig = requestConfig;
|
||||
}
|
||||
|
||||
public ClassicHttpRequest getRequest() {
|
||||
return request;
|
||||
}
|
||||
|
||||
public RequestConfig getRequestConfig() {
|
||||
return requestConfig;
|
||||
}
|
||||
}
|
||||
|
||||
private static final String NGINX_STATUS_NAME = "nginx_status";
|
||||
private static final String REQ_STATUS_NAME = "req_status";
|
||||
private static final String AVAILABLE = "available";
|
||||
@@ -93,34 +115,36 @@ public class NginxCollectImpl extends AbstractCollect {
|
||||
nginxProtocol.setUrl(url == null ? RIGHT_DASH : RIGHT_DASH + url.trim());
|
||||
}
|
||||
|
||||
HttpContext httpContext = createHttpContext(metrics.getNginx());
|
||||
HttpUriRequest request = createHttpRequest(metrics.getNginx());
|
||||
try (CloseableHttpResponse response = CommonHttpClient.getHttpClient().execute(request, httpContext)){
|
||||
// send an HTTP request and get the response data
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
HttpRequestWithConfig requestWithConfig = createHttpRequest(metrics.getNginx());
|
||||
HttpContext httpContext = createHttpContext(metrics.getNginx(), requestWithConfig.getRequestConfig());
|
||||
ClassicHttpRequest request = requestWithConfig.getRequest();
|
||||
|
||||
HttpClientResponseHandler<Void> responseHandler = response -> {
|
||||
int statusCode = response.getCode();
|
||||
if (statusCode != HttpStatus.SC_OK) {
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg(NetworkConstants.STATUS_CODE + statusCode);
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
String resp = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
|
||||
|
||||
Long responseTime = System.currentTimeMillis() - startTime;
|
||||
// call different parsing methods based on the metrics name
|
||||
if (StringUtils.equalsAny(metrics.getName(), NGINX_STATUS_NAME, AVAILABLE)) {
|
||||
if (NGINX_STATUS_NAME.equals(metrics.getName()) || AVAILABLE.equals(metrics.getName())) {
|
||||
parseNginxStatusResponse(builder, resp, metrics, responseTime);
|
||||
} else if (REQ_STATUS_NAME.equals(metrics.getName())) {
|
||||
parseReqStatusResponse(builder, resp, metrics, responseTime);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
try {
|
||||
CommonHttpClient.getHttpClient().execute(request, httpContext, responseHandler);
|
||||
} catch (Exception e) {
|
||||
String errorMsg = CommonUtil.getMessageFromThrowable(e);
|
||||
log.info(errorMsg);
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg(errorMsg);
|
||||
} finally {
|
||||
if (request != null) {
|
||||
request.abort();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -130,18 +154,21 @@ public class NginxCollectImpl extends AbstractCollect {
|
||||
return DispatchConstants.PROTOCOL_NGINX;
|
||||
}
|
||||
|
||||
private HttpContext createHttpContext(NginxProtocol nginxProtocol) {
|
||||
private HttpContext createHttpContext(NginxProtocol nginxProtocol, RequestConfig requestConfig) {
|
||||
HttpHost host = new HttpHost(nginxProtocol.getHost(), Integer.parseInt(nginxProtocol.getPort()));
|
||||
HttpClientContext httpClientContext = new HttpClientContext();
|
||||
httpClientContext.setTargetHost(host);
|
||||
HttpClientContext httpClientContext = HttpClientContext.create();
|
||||
if (requestConfig != null) {
|
||||
httpClientContext.setRequestConfig(requestConfig);
|
||||
}
|
||||
return httpClientContext;
|
||||
}
|
||||
|
||||
private HttpUriRequest createHttpRequest(NginxProtocol nginxProtocol) {
|
||||
RequestBuilder requestBuilder = RequestBuilder.get();
|
||||
@SuppressWarnings("deprecation")
|
||||
private HttpRequestWithConfig createHttpRequest(NginxProtocol nginxProtocol) {
|
||||
ClassicRequestBuilder requestBuilder = ClassicRequestBuilder.get();
|
||||
String portWithUri = nginxProtocol.getPort() + CollectUtil.replaceUriSpecialChar(nginxProtocol.getUrl());
|
||||
String host = nginxProtocol.getHost();
|
||||
|
||||
|
||||
if (IpDomainUtil.isHasSchema(host)) {
|
||||
requestBuilder.setUri(host + ":" + portWithUri);
|
||||
} else {
|
||||
@@ -162,16 +189,16 @@ public class NginxCollectImpl extends AbstractCollect {
|
||||
requestBuilder.addHeader(HttpHeaders.USER_AGENT, NetworkConstants.USER_AGENT);
|
||||
requestBuilder.addHeader(HttpHeaders.ACCEPT, MediaType.TEXT_PLAIN_VALUE);
|
||||
|
||||
RequestConfig requestConfig = null;
|
||||
int timeout = Integer.parseInt(nginxProtocol.getTimeout());
|
||||
if (timeout > 0) {
|
||||
RequestConfig requestConfig = RequestConfig.custom()
|
||||
.setConnectTimeout(timeout)
|
||||
.setSocketTimeout(timeout)
|
||||
requestConfig = RequestConfig.custom()
|
||||
.setConnectTimeout(Timeout.ofMilliseconds(timeout))
|
||||
.setResponseTimeout(Timeout.ofMilliseconds(timeout))
|
||||
.setRedirectsEnabled(true)
|
||||
.build();
|
||||
requestBuilder.setConfig(requestConfig);
|
||||
}
|
||||
return requestBuilder.build();
|
||||
return new HttpRequestWithConfig(requestBuilder.build(), requestConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -335,4 +362,4 @@ public class NginxCollectImpl extends AbstractCollect {
|
||||
return GET + result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+98
-63
@@ -6,7 +6,7 @@
|
||||
* (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
|
||||
* 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,
|
||||
@@ -47,24 +47,24 @@ 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;
|
||||
import org.apache.http.auth.AuthScope;
|
||||
import org.apache.http.auth.UsernamePasswordCredentials;
|
||||
import org.apache.http.client.AuthCache;
|
||||
import org.apache.http.client.ClientProtocolException;
|
||||
import org.apache.http.client.CredentialsProvider;
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpUriRequest;
|
||||
import org.apache.http.client.methods.RequestBuilder;
|
||||
import org.apache.http.client.protocol.HttpClientContext;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.auth.DigestScheme;
|
||||
import org.apache.http.impl.client.BasicAuthCache;
|
||||
import org.apache.http.impl.client.BasicCredentialsProvider;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
import org.apache.hc.core5.http.HttpHeaders;
|
||||
import org.apache.hc.core5.http.HttpHost;
|
||||
import org.apache.hc.core5.http.HttpStatus;
|
||||
import org.apache.hc.core5.http.ClassicHttpRequest;
|
||||
import org.apache.hc.client5.http.auth.AuthScope;
|
||||
import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
|
||||
import org.apache.hc.client5.http.auth.AuthCache;
|
||||
import org.apache.hc.client5.http.ClientProtocolException;
|
||||
import org.apache.hc.client5.http.config.RequestConfig;
|
||||
import org.apache.hc.core5.http.io.HttpClientResponseHandler;
|
||||
import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
|
||||
import org.apache.hc.client5.http.protocol.HttpClientContext;
|
||||
import org.apache.hc.core5.http.io.entity.StringEntity;
|
||||
import org.apache.hc.client5.http.impl.auth.DigestScheme;
|
||||
import org.apache.hc.client5.http.impl.auth.BasicAuthCache;
|
||||
import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
|
||||
import org.apache.hc.core5.http.protocol.HttpContext;
|
||||
import org.apache.hc.core5.util.Timeout;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -74,11 +74,32 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
@Slf4j
|
||||
public class PrometheusAutoCollectImpl implements PrometheusCollect {
|
||||
|
||||
|
||||
/**
|
||||
* Inner class to wrap HTTP request with its configuration
|
||||
*/
|
||||
private static class HttpRequestWithConfig {
|
||||
private final ClassicHttpRequest request;
|
||||
private final RequestConfig requestConfig;
|
||||
|
||||
public HttpRequestWithConfig(ClassicHttpRequest request, RequestConfig requestConfig) {
|
||||
this.request = request;
|
||||
this.requestConfig = requestConfig;
|
||||
}
|
||||
|
||||
public ClassicHttpRequest getRequest() {
|
||||
return request;
|
||||
}
|
||||
|
||||
public RequestConfig getRequestConfig() {
|
||||
return requestConfig;
|
||||
}
|
||||
}
|
||||
|
||||
private final Set<Integer> defaultSuccessStatusCodes = Stream.of(HttpStatus.SC_OK, HttpStatus.SC_CREATED,
|
||||
HttpStatus.SC_ACCEPTED, HttpStatus.SC_MULTIPLE_CHOICES, HttpStatus.SC_MOVED_PERMANENTLY,
|
||||
HttpStatus.SC_MOVED_TEMPORARILY).collect(Collectors.toSet());
|
||||
|
||||
|
||||
@Override
|
||||
public List<CollectRep.MetricsData> collect(CollectRep.MetricsData.Builder builder,
|
||||
Metrics metrics) {
|
||||
@@ -89,11 +110,13 @@ public class PrometheusAutoCollectImpl implements PrometheusCollect {
|
||||
builder.setMsg(e.getMessage());
|
||||
return null;
|
||||
}
|
||||
HttpContext httpContext = createHttpContext(metrics.getPrometheus());
|
||||
HttpUriRequest request = createHttpRequest(metrics.getPrometheus());
|
||||
try (CloseableHttpResponse response =
|
||||
CommonHttpClient.getHttpClient().execute(request, httpContext)) {
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
|
||||
HttpRequestWithConfig requestWithConfig = createHttpRequest(metrics.getPrometheus());
|
||||
ClassicHttpRequest request = requestWithConfig.getRequest();
|
||||
HttpContext httpContext = createHttpContext(metrics.getPrometheus(), requestWithConfig.getRequestConfig());
|
||||
|
||||
HttpClientResponseHandler<List<CollectRep.MetricsData>> responseHandler = response -> {
|
||||
int statusCode = response.getCode();
|
||||
boolean isSuccessInvoke = defaultSuccessStatusCodes.contains(statusCode);
|
||||
log.debug("http response status: {}", statusCode);
|
||||
if (!isSuccessInvoke) {
|
||||
@@ -102,12 +125,18 @@ public class PrometheusAutoCollectImpl implements PrometheusCollect {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
// Parse directly from the entity stream inside the handler loop while connection is open
|
||||
return parseResponseByPrometheusExporter(response.getEntity().getContent(), builder);
|
||||
} catch (Exception e) {
|
||||
log.info("parse error: {}.", e.getMessage(), e);
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg("parse response data error:" + e.getMessage());
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
return CommonHttpClient.getHttpClient().execute(request, httpContext, responseHandler);
|
||||
} catch (ClientProtocolException e1) {
|
||||
String errorMsg = CommonUtil.getMessageFromThrowable(e1);
|
||||
log.error(errorMsg);
|
||||
@@ -133,31 +162,27 @@ public class PrometheusAutoCollectImpl implements PrometheusCollect {
|
||||
log.error(errorMsg, e);
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg(errorMsg);
|
||||
} finally {
|
||||
if (request != null) {
|
||||
request.abort();
|
||||
}
|
||||
}
|
||||
return Collections.singletonList(builder.build());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String supportProtocol() {
|
||||
return DispatchConstants.PROTOCOL_PROMETHEUS;
|
||||
}
|
||||
|
||||
|
||||
private void validateParams(Metrics metrics) throws Exception {
|
||||
if (metrics == null || metrics.getPrometheus() == null) {
|
||||
throw new Exception("Prometheus collect must has prometheus params");
|
||||
}
|
||||
PrometheusProtocol protocol = metrics.getPrometheus();
|
||||
if (protocol.getPath() == null
|
||||
|| !StringUtils.hasText(protocol.getPath())
|
||||
|| !protocol.getPath().startsWith(RIGHT_DASH)) {
|
||||
|| !StringUtils.hasText(protocol.getPath())
|
||||
|| !protocol.getPath().startsWith(RIGHT_DASH)) {
|
||||
protocol.setPath(protocol.getPath() == null ? RIGHT_DASH : RIGHT_DASH + protocol.getPath().trim());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private List<CollectRep.MetricsData> parseResponseByPrometheusExporter(InputStream inputStream, CollectRep.MetricsData.Builder builder) throws IOException {
|
||||
long endTime = System.currentTimeMillis();
|
||||
builder.setTime(endTime);
|
||||
@@ -201,40 +226,49 @@ public class PrometheusAutoCollectImpl implements PrometheusCollect {
|
||||
}
|
||||
return metricsDataList;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* create httpContext
|
||||
*
|
||||
* @param protocol prometheus protocol
|
||||
* @param requestConfig request config
|
||||
* @return context
|
||||
*/
|
||||
public HttpContext createHttpContext(PrometheusProtocol protocol) {
|
||||
public HttpContext createHttpContext(PrometheusProtocol protocol, RequestConfig requestConfig) {
|
||||
HttpClientContext clientContext = HttpClientContext.create();
|
||||
|
||||
if (requestConfig != null) {
|
||||
clientContext.setRequestConfig(requestConfig);
|
||||
}
|
||||
|
||||
PrometheusProtocol.Authorization auth = protocol.getAuthorization();
|
||||
if (auth != null && DispatchConstants.DIGEST_AUTH.equals(auth.getType())) {
|
||||
HttpClientContext clientContext = new HttpClientContext();
|
||||
if (StringUtils.hasText(auth.getDigestAuthUsername())
|
||||
&& StringUtils.hasText(auth.getDigestAuthPassword())) {
|
||||
CredentialsProvider provider = new BasicCredentialsProvider();
|
||||
&& StringUtils.hasText(auth.getDigestAuthPassword())) {
|
||||
BasicCredentialsProvider provider = new BasicCredentialsProvider();
|
||||
UsernamePasswordCredentials credentials =
|
||||
new UsernamePasswordCredentials(auth.getDigestAuthUsername(), auth.getDigestAuthPassword());
|
||||
provider.setCredentials(AuthScope.ANY, credentials);
|
||||
new UsernamePasswordCredentials(auth.getDigestAuthUsername(), auth.getDigestAuthPassword().toCharArray());
|
||||
// Fix: Use specific AuthScope instead of AuthScope.ANY
|
||||
AuthScope authScope = new AuthScope(protocol.getHost(), Integer.parseInt(protocol.getPort()));
|
||||
provider.setCredentials(authScope, credentials);
|
||||
|
||||
AuthCache authCache = new BasicAuthCache();
|
||||
authCache.put(new HttpHost(protocol.getHost(), Integer.parseInt(protocol.getPort())), new DigestScheme());
|
||||
clientContext.setCredentialsProvider(provider);
|
||||
clientContext.setAuthCache(authCache);
|
||||
return clientContext;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return clientContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* create http request
|
||||
* @param protocol http params
|
||||
* @return http uri request
|
||||
* @return http uri request wrapper
|
||||
*/
|
||||
public HttpUriRequest createHttpRequest(PrometheusProtocol protocol) {
|
||||
RequestBuilder requestBuilder = RequestBuilder.get();
|
||||
@SuppressWarnings("deprecation")
|
||||
private HttpRequestWithConfig createHttpRequest(PrometheusProtocol protocol) {
|
||||
ClassicRequestBuilder requestBuilder = ClassicRequestBuilder.get();
|
||||
// params
|
||||
Map<String, String> params = protocol.getParams();
|
||||
if (params != null && !params.isEmpty()) {
|
||||
@@ -260,7 +294,7 @@ public class PrometheusAutoCollectImpl implements PrometheusCollect {
|
||||
}
|
||||
// add accept
|
||||
requestBuilder.addHeader(HttpHeaders.ACCEPT, MediaType.TEXT_PLAIN_VALUE);
|
||||
|
||||
|
||||
if (protocol.getAuthorization() != null) {
|
||||
PrometheusProtocol.Authorization authorization = protocol.getAuthorization();
|
||||
if (DispatchConstants.BEARER_TOKEN.equalsIgnoreCase(authorization.getType())) {
|
||||
@@ -268,7 +302,7 @@ public class PrometheusAutoCollectImpl implements PrometheusCollect {
|
||||
requestBuilder.addHeader(HttpHeaders.AUTHORIZATION, value);
|
||||
} else if (DispatchConstants.BASIC_AUTH.equals(authorization.getType())) {
|
||||
if (StringUtils.hasText(authorization.getBasicAuthUsername())
|
||||
&& StringUtils.hasText(authorization.getBasicAuthPassword())) {
|
||||
&& StringUtils.hasText(authorization.getBasicAuthPassword())) {
|
||||
String authStr = authorization.getBasicAuthUsername() + ":" + authorization.getBasicAuthPassword();
|
||||
String encodedAuth = Base64Util.encode(authStr);
|
||||
requestBuilder.addHeader(HttpHeaders.AUTHORIZATION, DispatchConstants.BASIC + " " + encodedAuth);
|
||||
@@ -280,17 +314,17 @@ public class PrometheusAutoCollectImpl implements PrometheusCollect {
|
||||
if (StringUtils.hasLength(protocol.getPayload()) && (HttpMethod.POST.matches(protocol.getMethod()) || HttpMethod.PUT.matches(protocol.getMethod()))) {
|
||||
requestBuilder.setEntity(new StringEntity(protocol.getPayload(), StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
|
||||
// uri
|
||||
String uri = CollectUtil.replaceUriSpecialChar(protocol.getPath());
|
||||
if (IpDomainUtil.isHasSchema(protocol.getHost())) {
|
||||
|
||||
|
||||
requestBuilder.setUri(protocol.getHost() + SignConstants.DOUBLE_MARK + protocol.getPort() + uri);
|
||||
} else {
|
||||
String ipAddressType = IpDomainUtil.checkIpAddressType(protocol.getHost());
|
||||
String baseUri = NetworkConstants.IPV6.equals(ipAddressType)
|
||||
? String.format("[%s]:%s%s", protocol.getHost(), protocol.getPort(), uri)
|
||||
: String.format("%s:%s%s", protocol.getHost(), protocol.getPort(), uri);
|
||||
? String.format("[%s]:%s%s", protocol.getHost(), protocol.getPort(), uri)
|
||||
: String.format("%s:%s%s", protocol.getHost(), protocol.getPort(), uri);
|
||||
boolean ssl = Boolean.parseBoolean(protocol.getSsl());
|
||||
if (ssl) {
|
||||
requestBuilder.setUri(NetworkConstants.HTTPS_HEADER + baseUri);
|
||||
@@ -298,18 +332,19 @@ public class PrometheusAutoCollectImpl implements PrometheusCollect {
|
||||
requestBuilder.setUri(NetworkConstants.HTTP_HEADER + baseUri);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
RequestConfig requestConfig = null;
|
||||
// custom timeout
|
||||
int timeout = CollectUtil.getTimeout(protocol.getTimeout(), 0);
|
||||
if (timeout > 0) {
|
||||
RequestConfig requestConfig = RequestConfig.custom()
|
||||
.setConnectTimeout(timeout)
|
||||
.setSocketTimeout(timeout)
|
||||
.setRedirectsEnabled(true)
|
||||
.build();
|
||||
requestBuilder.setConfig(requestConfig);
|
||||
requestConfig = RequestConfig.custom()
|
||||
.setConnectTimeout(Timeout.ofMilliseconds(timeout))
|
||||
.setResponseTimeout(Timeout.ofMilliseconds(timeout))
|
||||
.setRedirectsEnabled(true)
|
||||
.build();
|
||||
}
|
||||
return requestBuilder.build();
|
||||
|
||||
return new HttpRequestWithConfig(requestBuilder.build(), requestConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -319,11 +354,11 @@ public class PrometheusAutoCollectImpl implements PrometheusCollect {
|
||||
public static PrometheusAutoCollectImpl getInstance() {
|
||||
return PrometheusAutoCollectImpl.SingleInstance.INSTANCE;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* static instance
|
||||
*/
|
||||
private static class SingleInstance {
|
||||
private static final PrometheusAutoCollectImpl INSTANCE = new PrometheusAutoCollectImpl();
|
||||
}
|
||||
}
|
||||
}
|
||||
+93
-62
@@ -6,7 +6,7 @@
|
||||
* (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
|
||||
* 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,
|
||||
@@ -30,25 +30,25 @@ import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
import org.apache.hertzbeat.common.util.Base64Util;
|
||||
import org.apache.hertzbeat.common.util.CommonUtil;
|
||||
import org.apache.hertzbeat.common.util.IpDomainUtil;
|
||||
import org.apache.http.HttpHeaders;
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.apache.http.auth.AuthScope;
|
||||
import org.apache.http.auth.UsernamePasswordCredentials;
|
||||
import org.apache.http.client.AuthCache;
|
||||
import org.apache.http.client.ClientProtocolException;
|
||||
import org.apache.http.client.CredentialsProvider;
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpUriRequest;
|
||||
import org.apache.http.client.methods.RequestBuilder;
|
||||
import org.apache.http.client.protocol.HttpClientContext;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.auth.DigestScheme;
|
||||
import org.apache.http.impl.client.BasicAuthCache;
|
||||
import org.apache.http.impl.client.BasicCredentialsProvider;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.apache.hc.core5.http.HttpHeaders;
|
||||
import org.apache.hc.core5.http.HttpHost;
|
||||
import org.apache.hc.core5.http.HttpStatus;
|
||||
import org.apache.hc.core5.http.ClassicHttpRequest;
|
||||
import org.apache.hc.client5.http.auth.AuthScope;
|
||||
import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
|
||||
import org.apache.hc.client5.http.auth.AuthCache;
|
||||
import org.apache.hc.client5.http.ClientProtocolException;
|
||||
import org.apache.hc.client5.http.config.RequestConfig;
|
||||
import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
|
||||
import org.apache.hc.client5.http.protocol.HttpClientContext;
|
||||
import org.apache.hc.core5.http.io.entity.StringEntity;
|
||||
import org.apache.hc.client5.http.impl.auth.DigestScheme;
|
||||
import org.apache.hc.client5.http.impl.auth.BasicAuthCache;
|
||||
import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
|
||||
import org.apache.hc.core5.http.protocol.HttpContext;
|
||||
import org.apache.hc.core5.http.io.entity.EntityUtils;
|
||||
import org.apache.hc.core5.util.Timeout;
|
||||
import org.apache.hc.core5.http.io.HttpClientResponseHandler;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -71,6 +71,27 @@ import static org.apache.hertzbeat.common.constants.SignConstants.RIGHT_DASH;
|
||||
@Slf4j
|
||||
public class PrometheusProxyCollectImpl implements PrometheusCollect {
|
||||
|
||||
/**
|
||||
* Inner class to wrap HTTP request with its configuration
|
||||
*/
|
||||
private static class HttpRequestWithConfig {
|
||||
private final ClassicHttpRequest request;
|
||||
private final RequestConfig requestConfig;
|
||||
|
||||
public HttpRequestWithConfig(ClassicHttpRequest request, RequestConfig requestConfig) {
|
||||
this.request = request;
|
||||
this.requestConfig = requestConfig;
|
||||
}
|
||||
|
||||
public ClassicHttpRequest getRequest() {
|
||||
return request;
|
||||
}
|
||||
|
||||
public RequestConfig getRequestConfig() {
|
||||
return requestConfig;
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
@@ -80,7 +101,7 @@ public class PrometheusProxyCollectImpl implements PrometheusCollect {
|
||||
@Override
|
||||
public List<CollectRep.MetricsData> collect(CollectRep.MetricsData.Builder builder, Metrics metrics) {
|
||||
PrometheusProtocol prometheusProtocol = metrics.getPrometheus();
|
||||
HttpUriRequest request;
|
||||
|
||||
try {
|
||||
validateParams(metrics);
|
||||
} catch (Exception e) {
|
||||
@@ -89,17 +110,19 @@ public class PrometheusProxyCollectImpl implements PrometheusCollect {
|
||||
return Collections.singletonList(builder.build());
|
||||
}
|
||||
|
||||
HttpContext httpContext = createHttpContext(prometheusProtocol);
|
||||
request = createHttpRequest(prometheusProtocol);
|
||||
HttpRequestWithConfig requestWithConfig = createHttpRequest(prometheusProtocol);
|
||||
ClassicHttpRequest request = requestWithConfig.getRequest();
|
||||
HttpContext httpContext = createHttpContext(prometheusProtocol, requestWithConfig.getRequestConfig());
|
||||
|
||||
try (CloseableHttpResponse response = CommonHttpClient.getHttpClient().execute(request, httpContext)) {
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
// Use HttpClientResponseHandler to avoid deprecated execute method and manual resource closing
|
||||
HttpClientResponseHandler<Void> responseHandler = response -> {
|
||||
int statusCode = response.getCode();
|
||||
log.debug("Prometheus proxy collect, response status: {}", statusCode);
|
||||
|
||||
if (!defaultSuccessStatusCodes.contains(statusCode)) {
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg(NetworkConstants.STATUS_CODE + SignConstants.BLANK + statusCode);
|
||||
return Collections.singletonList(builder.build());
|
||||
return null;
|
||||
}
|
||||
|
||||
String rawTextContent = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
|
||||
@@ -118,6 +141,11 @@ public class PrometheusProxyCollectImpl implements PrometheusCollect {
|
||||
builder.addValueRow(valueRowBuilder.build());
|
||||
|
||||
builder.setCode(CollectRep.Code.SUCCESS);
|
||||
return null;
|
||||
};
|
||||
|
||||
try {
|
||||
CommonHttpClient.getHttpClient().execute(request, httpContext, responseHandler);
|
||||
} catch (ClientProtocolException e1) {
|
||||
String errorMsg = CommonUtil.getMessageFromThrowable(e1);
|
||||
log.error("Prometheus proxy collect error: {}. Host: {}, Port: {}", errorMsg, prometheusProtocol.getHost(), prometheusProtocol.getPort(), e1);
|
||||
@@ -143,10 +171,6 @@ public class PrometheusProxyCollectImpl implements PrometheusCollect {
|
||||
log.error("Prometheus proxy collect unknown error: {}. Host: {}, Port: {}", errorMsg, prometheusProtocol.getHost(), prometheusProtocol.getPort(), e);
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg(errorMsg);
|
||||
} finally {
|
||||
if (request != null) {
|
||||
request.abort();
|
||||
}
|
||||
}
|
||||
return Collections.singletonList(builder.build());
|
||||
}
|
||||
@@ -162,7 +186,7 @@ public class PrometheusProxyCollectImpl implements PrometheusCollect {
|
||||
}
|
||||
PrometheusProtocol protocol = metrics.getPrometheus();
|
||||
if (!StringUtils.hasText(protocol.getHost())
|
||||
|| !StringUtils.hasText(protocol.getPort())) {
|
||||
|| !StringUtils.hasText(protocol.getPort())) {
|
||||
throw new Exception("Prometheus collect must has host and port params");
|
||||
}
|
||||
if (protocol.getPath() == null
|
||||
@@ -171,42 +195,51 @@ public class PrometheusProxyCollectImpl implements PrometheusCollect {
|
||||
protocol.setPath(protocol.getPath() == null ? RIGHT_DASH : RIGHT_DASH + protocol.getPath().trim());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* create httpContext
|
||||
* This method is adapted from PrometheusAutoCollectImpl
|
||||
* @param protocol prometheus protocol
|
||||
* @param requestConfig request config
|
||||
* @return context
|
||||
*/
|
||||
public HttpContext createHttpContext(PrometheusProtocol protocol) {
|
||||
public HttpContext createHttpContext(PrometheusProtocol protocol, RequestConfig requestConfig) {
|
||||
HttpClientContext clientContext = HttpClientContext.create();
|
||||
|
||||
if (requestConfig != null) {
|
||||
clientContext.setRequestConfig(requestConfig);
|
||||
}
|
||||
|
||||
PrometheusProtocol.Authorization auth = protocol.getAuthorization();
|
||||
if (auth != null && DispatchConstants.DIGEST_AUTH.equals(auth.getType())) {
|
||||
HttpClientContext clientContext = new HttpClientContext();
|
||||
if (StringUtils.hasText(auth.getDigestAuthUsername())
|
||||
&& StringUtils.hasText(auth.getDigestAuthPassword())) {
|
||||
CredentialsProvider provider = new BasicCredentialsProvider();
|
||||
&& StringUtils.hasText(auth.getDigestAuthPassword())) {
|
||||
BasicCredentialsProvider provider = new BasicCredentialsProvider();
|
||||
UsernamePasswordCredentials credentials =
|
||||
new UsernamePasswordCredentials(auth.getDigestAuthUsername(), auth.getDigestAuthPassword());
|
||||
provider.setCredentials(AuthScope.ANY, credentials);
|
||||
new UsernamePasswordCredentials(auth.getDigestAuthUsername(), auth.getDigestAuthPassword().toCharArray());
|
||||
// Fix: Use specific AuthScope instead of AuthScope.ANY
|
||||
AuthScope authScope = new AuthScope(protocol.getHost(), Integer.parseInt(protocol.getPort()));
|
||||
provider.setCredentials(authScope, credentials);
|
||||
|
||||
AuthCache authCache = new BasicAuthCache();
|
||||
HttpHost targetHost = new HttpHost(protocol.getHost(), Integer.parseInt(protocol.getPort()));
|
||||
authCache.put(targetHost, new DigestScheme());
|
||||
clientContext.setCredentialsProvider(provider);
|
||||
clientContext.setAuthCache(authCache);
|
||||
return clientContext;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return clientContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* create http request
|
||||
* This method is adapted from PrometheusAutoCollectImpl
|
||||
* @param protocol http params
|
||||
* @return http uri request
|
||||
* @return http uri request wrapper
|
||||
*/
|
||||
public HttpUriRequest createHttpRequest(PrometheusProtocol protocol) {
|
||||
RequestBuilder requestBuilder = RequestBuilder.get();
|
||||
@SuppressWarnings("deprecation")
|
||||
private HttpRequestWithConfig createHttpRequest(PrometheusProtocol protocol) {
|
||||
ClassicRequestBuilder requestBuilder = ClassicRequestBuilder.get();
|
||||
// params
|
||||
Map<String, String> params = protocol.getParams();
|
||||
if (params != null && !params.isEmpty()) {
|
||||
@@ -231,7 +264,7 @@ public class PrometheusProxyCollectImpl implements PrometheusCollect {
|
||||
if (headers == null || headers.keySet().stream().noneMatch(HttpHeaders.ACCEPT::equalsIgnoreCase)) {
|
||||
requestBuilder.addHeader(HttpHeaders.ACCEPT, MediaType.TEXT_PLAIN_VALUE + ";version=0.0.4,*/*;q=0.1");
|
||||
}
|
||||
|
||||
|
||||
if (protocol.getAuthorization() != null) {
|
||||
PrometheusProtocol.Authorization authorization = protocol.getAuthorization();
|
||||
if (DispatchConstants.BEARER_TOKEN.equalsIgnoreCase(authorization.getType())) {
|
||||
@@ -241,7 +274,7 @@ public class PrometheusProxyCollectImpl implements PrometheusCollect {
|
||||
}
|
||||
} else if (DispatchConstants.BASIC_AUTH.equals(authorization.getType())) {
|
||||
if (StringUtils.hasText(authorization.getBasicAuthUsername())
|
||||
&& StringUtils.hasText(authorization.getBasicAuthPassword())) {
|
||||
&& StringUtils.hasText(authorization.getBasicAuthPassword())) {
|
||||
String authStr = authorization.getBasicAuthUsername() + ":" + authorization.getBasicAuthPassword();
|
||||
String encodedAuth = Base64Util.encode(authStr);
|
||||
requestBuilder.addHeader(HttpHeaders.AUTHORIZATION, DispatchConstants.BASIC + " " + encodedAuth);
|
||||
@@ -255,15 +288,15 @@ public class PrometheusProxyCollectImpl implements PrometheusCollect {
|
||||
requestBuilder.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
String uriPath = CollectUtil.replaceUriSpecialChar(protocol.getPath());
|
||||
if (IpDomainUtil.isHasSchema(protocol.getHost())) {
|
||||
requestBuilder.setUri(protocol.getHost() + SignConstants.DOUBLE_MARK + protocol.getPort() + uriPath);
|
||||
} else {
|
||||
String ipAddressType = IpDomainUtil.checkIpAddressType(protocol.getHost());
|
||||
String baseUri = NetworkConstants.IPV6.equals(ipAddressType)
|
||||
? String.format("[%s]:%s%s", protocol.getHost(), protocol.getPort(), uriPath)
|
||||
: String.format("%s:%s%s", protocol.getHost(), protocol.getPort(), uriPath);
|
||||
? String.format("[%s]:%s%s", protocol.getHost(), protocol.getPort(), uriPath)
|
||||
: String.format("%s:%s%s", protocol.getHost(), protocol.getPort(), uriPath);
|
||||
boolean ssl = Boolean.parseBoolean(protocol.getSsl());
|
||||
if (ssl) {
|
||||
requestBuilder.setUri(NetworkConstants.HTTPS_HEADER + baseUri);
|
||||
@@ -271,24 +304,22 @@ public class PrometheusProxyCollectImpl implements PrometheusCollect {
|
||||
requestBuilder.setUri(NetworkConstants.HTTP_HEADER + baseUri);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
RequestConfig requestConfig;
|
||||
// custom timeout
|
||||
int timeout = CollectUtil.getTimeout(protocol.getTimeout());
|
||||
if (timeout > 0) {
|
||||
RequestConfig requestConfig = RequestConfig.custom()
|
||||
.setConnectTimeout(timeout)
|
||||
.setSocketTimeout(timeout)
|
||||
.setConnectionRequestTimeout(timeout)
|
||||
.setRedirectsEnabled(true)
|
||||
.build();
|
||||
requestBuilder.setConfig(requestConfig);
|
||||
requestConfig = RequestConfig.custom()
|
||||
.setConnectTimeout(Timeout.ofMilliseconds(timeout))
|
||||
.setResponseTimeout(Timeout.ofMilliseconds(timeout))
|
||||
.setRedirectsEnabled(true)
|
||||
.build();
|
||||
} else {
|
||||
RequestConfig requestConfig = RequestConfig.custom()
|
||||
.setRedirectsEnabled(true)
|
||||
.build();
|
||||
requestBuilder.setConfig(requestConfig);
|
||||
requestConfig = RequestConfig.custom()
|
||||
.setRedirectsEnabled(true)
|
||||
.build();
|
||||
}
|
||||
return requestBuilder.build();
|
||||
return new HttpRequestWithConfig(requestBuilder.build(), requestConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -305,4 +336,4 @@ public class PrometheusProxyCollectImpl implements PrometheusCollect {
|
||||
private static class SingleInstance {
|
||||
private static final PrometheusProxyCollectImpl INSTANCE = new PrometheusProxyCollectImpl();
|
||||
}
|
||||
}
|
||||
}
|
||||
+59
-34
@@ -6,7 +6,7 @@
|
||||
* (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
|
||||
* 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,
|
||||
@@ -24,6 +24,15 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hc.client5.http.config.RequestConfig;
|
||||
import org.apache.hc.client5.http.protocol.HttpClientContext;
|
||||
import org.apache.hc.core5.http.ClassicHttpRequest;
|
||||
import org.apache.hc.core5.http.HttpHeaders;
|
||||
import org.apache.hc.core5.http.io.HttpClientResponseHandler;
|
||||
import org.apache.hc.core5.http.io.entity.EntityUtils;
|
||||
import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
|
||||
import org.apache.hc.core5.http.protocol.HttpContext;
|
||||
import org.apache.hc.core5.util.Timeout;
|
||||
import org.apache.hertzbeat.collector.collect.AbstractCollect;
|
||||
import org.apache.hertzbeat.collector.collect.common.http.CommonHttpClient;
|
||||
import org.apache.hertzbeat.collector.dispatch.DispatchConstants;
|
||||
@@ -38,15 +47,6 @@ import org.apache.hertzbeat.common.entity.push.PushMetricsDto;
|
||||
import org.apache.hertzbeat.common.util.CommonUtil;
|
||||
import org.apache.hertzbeat.common.util.IpDomainUtil;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.apache.http.HttpHeaders;
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpUriRequest;
|
||||
import org.apache.http.client.methods.RequestBuilder;
|
||||
import org.apache.http.client.protocol.HttpClientContext;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
/**
|
||||
@@ -55,6 +55,27 @@ import org.springframework.http.MediaType;
|
||||
@Slf4j
|
||||
public class PushCollectImpl extends AbstractCollect {
|
||||
|
||||
/**
|
||||
* Inner class to wrap HTTP request with its configuration
|
||||
*/
|
||||
private static class HttpRequestWithConfig {
|
||||
private final ClassicHttpRequest request;
|
||||
private final RequestConfig requestConfig;
|
||||
|
||||
public HttpRequestWithConfig(ClassicHttpRequest request, RequestConfig requestConfig) {
|
||||
this.request = request;
|
||||
this.requestConfig = requestConfig;
|
||||
}
|
||||
|
||||
public ClassicHttpRequest getRequest() {
|
||||
return request;
|
||||
}
|
||||
|
||||
public RequestConfig getRequestConfig() {
|
||||
return requestConfig;
|
||||
}
|
||||
}
|
||||
|
||||
private static final Map<Long, Long> timeMap = new ConcurrentHashMap<>();
|
||||
|
||||
// ms
|
||||
@@ -83,20 +104,25 @@ public class PushCollectImpl extends AbstractCollect {
|
||||
Long time = timeMap.getOrDefault(monitorId, curTime - FIRST_COLLECT_INTERVAL);
|
||||
timeMap.put(monitorId, curTime);
|
||||
|
||||
HttpContext httpContext = createHttpContext(pushProtocol);
|
||||
HttpUriRequest request = createHttpRequest(pushProtocol, monitorId, time);
|
||||
HttpRequestWithConfig requestWithConfig = createHttpRequest(pushProtocol, monitorId, time);
|
||||
ClassicHttpRequest request = requestWithConfig.getRequest();
|
||||
HttpContext httpContext = createHttpContext(pushProtocol, requestWithConfig.getRequestConfig());
|
||||
|
||||
try (CloseableHttpResponse response = CommonHttpClient.getHttpClient().execute(request, httpContext)) {
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
HttpClientResponseHandler<String> responseHandler = response -> {
|
||||
int statusCode = response.getCode();
|
||||
if (statusCode != SUCCESS_CODE) {
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg(NetworkConstants.STATUS_CODE + SignConstants.BLANK + statusCode);
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
String resp = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
|
||||
|
||||
parseResponse(builder, resp, metrics);
|
||||
return EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
|
||||
};
|
||||
|
||||
try {
|
||||
String resp = CommonHttpClient.getHttpClient().execute(request, httpContext, responseHandler);
|
||||
if (resp != null) {
|
||||
parseResponse(builder, resp, metrics);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
String errorMsg = CommonUtil.getMessageFromThrowable(e);
|
||||
log.error(errorMsg, e);
|
||||
@@ -111,16 +137,17 @@ public class PushCollectImpl extends AbstractCollect {
|
||||
return DispatchConstants.PROTOCOL_PUSH;
|
||||
}
|
||||
|
||||
private HttpContext createHttpContext(PushProtocol pushProtocol) {
|
||||
HttpHost host = new HttpHost(pushProtocol.getHost(), Integer.parseInt(pushProtocol.getPort()));
|
||||
HttpClientContext httpClientContext = new HttpClientContext();
|
||||
httpClientContext.setTargetHost(host);
|
||||
private HttpContext createHttpContext(PushProtocol pushProtocol, RequestConfig requestConfig) {
|
||||
HttpClientContext httpClientContext = HttpClientContext.create();
|
||||
if (requestConfig != null) {
|
||||
httpClientContext.setRequestConfig(requestConfig);
|
||||
}
|
||||
return httpClientContext;
|
||||
}
|
||||
|
||||
private HttpUriRequest createHttpRequest(PushProtocol pushProtocol, Long monitorId, Long startTime) {
|
||||
RequestBuilder requestBuilder = RequestBuilder.get();
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private HttpRequestWithConfig createHttpRequest(PushProtocol pushProtocol, Long monitorId, Long startTime) {
|
||||
ClassicRequestBuilder requestBuilder = ClassicRequestBuilder.get();
|
||||
|
||||
// uri
|
||||
String uri = CollectUtil.replaceUriSpecialChar(pushProtocol.getUri());
|
||||
@@ -142,19 +169,17 @@ public class PushCollectImpl extends AbstractCollect {
|
||||
requestBuilder.addParameter("time", String.valueOf(startTime));
|
||||
requestBuilder.addHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
|
||||
|
||||
|
||||
//requestBuilder.setUri(pushProtocol.getUri());
|
||||
|
||||
RequestConfig requestConfig = null;
|
||||
if (DEFAULT_TIMEOUT > 0) {
|
||||
RequestConfig requestConfig = RequestConfig.custom()
|
||||
.setConnectTimeout(DEFAULT_TIMEOUT)
|
||||
.setSocketTimeout(DEFAULT_TIMEOUT)
|
||||
// Using deprecated setConnectTimeout for request-level override
|
||||
requestConfig = RequestConfig.custom()
|
||||
.setConnectTimeout(Timeout.ofMilliseconds(DEFAULT_TIMEOUT))
|
||||
.setResponseTimeout(Timeout.ofMilliseconds(DEFAULT_TIMEOUT))
|
||||
.setRedirectsEnabled(true)
|
||||
.build();
|
||||
requestBuilder.setConfig(requestConfig);
|
||||
}
|
||||
|
||||
return requestBuilder.build();
|
||||
return new HttpRequestWithConfig(requestBuilder.build(), requestConfig);
|
||||
}
|
||||
|
||||
private void parseResponse(CollectRep.MetricsData.Builder builder, String resp, Metrics metric) {
|
||||
@@ -180,4 +205,4 @@ public class PushCollectImpl extends AbstractCollect {
|
||||
}
|
||||
builder.setTime(System.currentTimeMillis());
|
||||
}
|
||||
}
|
||||
}
|
||||
+39
-35
@@ -6,7 +6,7 @@
|
||||
* (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
|
||||
* 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,
|
||||
@@ -18,20 +18,20 @@
|
||||
package org.apache.hertzbeat.collector.collect.redfish;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import org.apache.hc.client5.http.config.RequestConfig;
|
||||
import org.apache.hc.client5.http.protocol.HttpClientContext;
|
||||
import org.apache.hc.core5.http.ClassicHttpRequest;
|
||||
import org.apache.hc.core5.http.HttpHeaders;
|
||||
import org.apache.hc.core5.http.HttpStatus;
|
||||
import org.apache.hc.core5.http.io.HttpClientResponseHandler;
|
||||
import org.apache.hc.core5.http.io.entity.StringEntity;
|
||||
import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
|
||||
import org.apache.hc.core5.util.Timeout;
|
||||
import org.apache.hertzbeat.collector.collect.common.http.CommonHttpClient;
|
||||
import org.apache.hertzbeat.common.constants.NetworkConstants;
|
||||
import org.apache.hertzbeat.common.constants.SignConstants;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.RedfishProtocol;
|
||||
import org.apache.hertzbeat.common.util.IpDomainUtil;
|
||||
import org.apache.http.HttpHeaders;
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpUriRequest;
|
||||
import org.apache.http.client.methods.RequestBuilder;
|
||||
import org.apache.http.client.protocol.HttpClientContext;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
/**
|
||||
@@ -58,11 +58,21 @@ public class RedfishClient {
|
||||
redfishProtocol.getUsername(), redfishProtocol.getPassword(), Integer.parseInt(redfishProtocol.getTimeout()));
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public ConnectSession connect() throws Exception {
|
||||
HttpHost host = new HttpHost(this.host, this.port);
|
||||
HttpClientContext httpClientContext = new HttpClientContext();
|
||||
httpClientContext.setTargetHost(host);
|
||||
RequestBuilder requestBuilder = RequestBuilder.post();
|
||||
HttpClientContext httpClientContext = HttpClientContext.create();
|
||||
|
||||
// Configure RequestConfig if timeout is set
|
||||
if (this.timeout > 0) {
|
||||
RequestConfig requestConfig = RequestConfig.custom()
|
||||
.setConnectTimeout(Timeout.ofMilliseconds(this.timeout))
|
||||
.setResponseTimeout(Timeout.ofMilliseconds(this.timeout))
|
||||
.setRedirectsEnabled(true)
|
||||
.build();
|
||||
httpClientContext.setRequestConfig(requestConfig);
|
||||
}
|
||||
|
||||
ClassicRequestBuilder requestBuilder = ClassicRequestBuilder.post();
|
||||
|
||||
String uri = REDFISH_SESSION_SERVICE;
|
||||
if (IpDomainUtil.isHasSchema(this.host)) {
|
||||
@@ -79,37 +89,31 @@ public class RedfishClient {
|
||||
requestBuilder.addHeader(HttpHeaders.CONNECTION, NetworkConstants.KEEP_ALIVE);
|
||||
requestBuilder.addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
|
||||
requestBuilder.addHeader(HttpHeaders.USER_AGENT, NetworkConstants.USER_AGENT);
|
||||
requestBuilder.addHeader(HttpHeaders.CONTENT_ENCODING, StandardCharsets.UTF_8 + "");
|
||||
// Content-Encoding header is usually for compression, setting charset is done in Content-Type usually,
|
||||
// but keeping original logic of setting it explicitly if that was the intent, though standard is usually empty or gzip.
|
||||
// original: requestBuilder.addHeader(HttpHeaders.CONTENT_ENCODING, StandardCharsets.UTF_8 + "");
|
||||
|
||||
final String json = "{\"UserName\": \"" + this.username + "\", \"Password\": \"" + this.password + "\"}";
|
||||
StringEntity entity = new StringEntity(json, StandardCharsets.UTF_8);
|
||||
requestBuilder.setEntity(entity);
|
||||
|
||||
if (this.timeout > 0) {
|
||||
RequestConfig requestConfig = RequestConfig.custom()
|
||||
.setConnectTimeout(this.timeout)
|
||||
.setSocketTimeout(this.timeout)
|
||||
.setRedirectsEnabled(true)
|
||||
.build();
|
||||
requestBuilder.setConfig(requestConfig);
|
||||
}
|
||||
ClassicHttpRequest request = requestBuilder.build();
|
||||
|
||||
HttpUriRequest request = requestBuilder.build();
|
||||
|
||||
Session session;
|
||||
try (CloseableHttpResponse response = CommonHttpClient.getHttpClient().execute(request, httpClientContext)) {
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
HttpClientResponseHandler<Session> responseHandler = response -> {
|
||||
int statusCode = response.getCode();
|
||||
if (statusCode != HttpStatus.SC_CREATED) {
|
||||
throw new Exception(NetworkConstants.STATUS_CODE + SignConstants.BLANK + statusCode);
|
||||
throw new org.apache.hc.client5.http.ClientProtocolException(NetworkConstants.STATUS_CODE + SignConstants.BLANK + statusCode);
|
||||
}
|
||||
String location = response.getFirstHeader(NetworkConstants.LOCATION).getValue();
|
||||
String auth = response.getFirstHeader(NetworkConstants.X_AUTH_TOKEN).getValue();
|
||||
session = new Session(auth, location, this.host, this.port);
|
||||
return new Session(auth, location, this.host, this.port);
|
||||
};
|
||||
|
||||
try {
|
||||
Session session = CommonHttpClient.getHttpClient().execute(request, httpClientContext, responseHandler);
|
||||
return new RedfishConnectSession(session);
|
||||
} catch (Exception e) {
|
||||
throw new Exception("Redfish session create error: " + e.getMessage());
|
||||
} finally {
|
||||
request.abort();
|
||||
throw new Exception("Redfish session create error: " + e.getMessage(), e);
|
||||
}
|
||||
return new RedfishConnectSession(session);
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
-19
@@ -6,7 +6,7 @@
|
||||
* (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
|
||||
* 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,
|
||||
@@ -18,15 +18,15 @@
|
||||
package org.apache.hertzbeat.collector.collect.redfish;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import org.apache.hc.client5.http.classic.methods.HttpDelete;
|
||||
import org.apache.hc.client5.http.classic.methods.HttpGet;
|
||||
import org.apache.hc.core5.http.HttpStatus;
|
||||
import org.apache.hc.core5.http.io.HttpClientResponseHandler;
|
||||
import org.apache.hc.core5.http.io.entity.EntityUtils;
|
||||
import org.apache.hertzbeat.collector.collect.common.http.CommonHttpClient;
|
||||
import org.apache.hertzbeat.common.constants.NetworkConstants;
|
||||
import org.apache.hertzbeat.common.constants.SignConstants;
|
||||
import org.apache.hertzbeat.common.util.IpDomainUtil;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpDelete;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
|
||||
/**
|
||||
* Redfish connect session
|
||||
@@ -54,15 +54,20 @@ public class RedfishConnectSession implements ConnectSession {
|
||||
HttpDelete httpDelete = new HttpDelete(url);
|
||||
httpDelete.setHeader(NetworkConstants.X_AUTH_TOKEN, session.token());
|
||||
httpDelete.setHeader(NetworkConstants.LOCATION, session.location());
|
||||
try (CloseableHttpResponse response = CommonHttpClient.getHttpClient().execute(httpDelete)) {
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
|
||||
HttpClientResponseHandler<Void> responseHandler = response -> {
|
||||
int statusCode = response.getCode();
|
||||
if (statusCode != HttpStatus.SC_OK) {
|
||||
throw new Exception(NetworkConstants.STATUS_CODE + SignConstants.BLANK + statusCode);
|
||||
throw new org.apache.hc.client5.http.ClientProtocolException(NetworkConstants.STATUS_CODE + SignConstants.BLANK + statusCode);
|
||||
}
|
||||
EntityUtils.consume(response.getEntity());
|
||||
return null;
|
||||
};
|
||||
|
||||
try {
|
||||
CommonHttpClient.getHttpClient().execute(httpDelete, responseHandler);
|
||||
} catch (Exception e) {
|
||||
throw new Exception("Redfish session close error:" + e.getMessage());
|
||||
} finally {
|
||||
httpDelete.abort();
|
||||
throw new Exception("Redfish session close error:" + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,16 +89,19 @@ public class RedfishConnectSession implements ConnectSession {
|
||||
HttpGet httpGet = new HttpGet(url);
|
||||
httpGet.setHeader(NetworkConstants.X_AUTH_TOKEN, session.token());
|
||||
httpGet.setHeader(NetworkConstants.LOCATION, session.location());
|
||||
try (CloseableHttpResponse response = CommonHttpClient.getHttpClient().execute(httpGet)) {
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
|
||||
HttpClientResponseHandler<String> responseHandler = response -> {
|
||||
int statusCode = response.getCode();
|
||||
if (statusCode != HttpStatus.SC_OK) {
|
||||
throw new Exception(NetworkConstants.STATUS_CODE + SignConstants.BLANK + statusCode);
|
||||
throw new org.apache.hc.client5.http.ClientProtocolException(NetworkConstants.STATUS_CODE + SignConstants.BLANK + statusCode);
|
||||
}
|
||||
return EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
|
||||
};
|
||||
|
||||
try {
|
||||
return CommonHttpClient.getHttpClient().execute(httpGet, responseHandler);
|
||||
} catch (Exception e) {
|
||||
throw new Exception("Redfish session get resource error:" + e.getMessage());
|
||||
} finally {
|
||||
httpGet.abort();
|
||||
throw new Exception("Redfish session get resource error:" + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-15
@@ -67,8 +67,6 @@ public class RedisCommonCollectImpl extends AbstractCollect {
|
||||
|
||||
private static final String CLUSTER = "3";
|
||||
|
||||
private static final String SINGLE = "1";
|
||||
|
||||
private static final String CLUSTER_INFO = "cluster";
|
||||
|
||||
private static final String UNIQUE_IDENTITY = "identity";
|
||||
@@ -142,7 +140,7 @@ public class RedisCommonCollectImpl extends AbstractCollect {
|
||||
* @return data
|
||||
*/
|
||||
private List<Map<String, String>> getClusterRedisInfo(Metrics metrics) throws GeneralSecurityException, IOException {
|
||||
Map<String, StatefulRedisConnection<String, String>> connectionMap = getConnectionList(metrics.getRedis());
|
||||
Map<String, StatefulRedisClusterConnection<String, String>> connectionMap = getConnectionList(metrics.getRedis());
|
||||
List<Map<String, String>> list = new ArrayList<>(connectionMap.size());
|
||||
connectionMap.forEach((identity, connection) ->{
|
||||
String info = connection.sync().info(metrics.getName());
|
||||
@@ -216,23 +214,16 @@ public class RedisCommonCollectImpl extends AbstractCollect {
|
||||
* @param redisProtocol protocol
|
||||
* @return connection map
|
||||
*/
|
||||
private Map<String, StatefulRedisConnection<String, String>> getConnectionList(RedisProtocol redisProtocol) throws GeneralSecurityException, IOException {
|
||||
private Map<String, StatefulRedisClusterConnection<String, String>> getConnectionList(RedisProtocol redisProtocol) throws GeneralSecurityException, IOException {
|
||||
// first connection
|
||||
StatefulRedisClusterConnection<String, String> connection = getClusterConnection(redisProtocol);
|
||||
Partitions partitions = connection.getPartitions();
|
||||
Map<String, StatefulRedisConnection<String, String>> clusterConnectionMap = new HashMap<>(partitions.size());
|
||||
Map<String, StatefulRedisClusterConnection<String, String>> clusterConnectionMap = new HashMap<>(partitions.size());
|
||||
for (RedisClusterNode partition : partitions) {
|
||||
RedisURI uri = partition.getUri();
|
||||
RedisProtocol singleRedisProtocol = RedisProtocol.builder()
|
||||
.host(uri.getHost())
|
||||
.port(String.valueOf(uri.getPort()))
|
||||
.username(redisProtocol.getUsername())
|
||||
.password(redisProtocol.getPassword())
|
||||
.pattern(SINGLE)
|
||||
.timeout(redisProtocol.getTimeout())
|
||||
.sshTunnel(redisProtocol.getSshTunnel())
|
||||
.build();
|
||||
StatefulRedisConnection<String, String> clusterConnection = getSingleConnection(singleRedisProtocol);
|
||||
redisProtocol.setHost(uri.getHost());
|
||||
redisProtocol.setPort(String.valueOf(uri.getPort()));
|
||||
StatefulRedisClusterConnection<String, String> clusterConnection = getClusterConnection(redisProtocol);
|
||||
clusterConnectionMap.put(doUri(uri.getHost(), uri.getPort()), clusterConnection);
|
||||
}
|
||||
return clusterConnectionMap;
|
||||
|
||||
+44
-27
@@ -6,7 +6,7 @@
|
||||
* (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
|
||||
* 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,
|
||||
@@ -22,6 +22,10 @@ import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hc.core5.http.ClassicHttpRequest;
|
||||
import org.apache.hc.core5.http.io.HttpClientResponseHandler;
|
||||
import org.apache.hc.core5.http.io.entity.EntityUtils;
|
||||
import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
|
||||
import org.apache.hertzbeat.collector.collect.AbstractCollect;
|
||||
import org.apache.hertzbeat.collector.collect.common.http.CommonHttpClient;
|
||||
import org.apache.hertzbeat.collector.dispatch.DispatchConstants;
|
||||
@@ -32,10 +36,6 @@ import org.apache.hertzbeat.common.entity.sd.ConnectionConfig;
|
||||
import org.apache.hertzbeat.common.entity.sd.EurekaDiscoveryResponseEntity;
|
||||
import org.apache.hertzbeat.common.util.CommonUtil;
|
||||
import org.apache.hertzbeat.common.util.XmlUtil;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpUriRequest;
|
||||
import org.apache.http.client.methods.RequestBuilder;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
@@ -57,38 +57,55 @@ public class EurekaSdCollectImpl extends AbstractCollect {
|
||||
@Override
|
||||
public void collect(CollectRep.MetricsData.Builder builder, Metrics metrics) {
|
||||
List<ConnectionConfig> configList = Lists.newArrayList();
|
||||
HttpUriRequest request = RequestBuilder.get().setUri(metrics.getEureka_sd().getUrl() + APP_LIST_PATH).build();
|
||||
try (CloseableHttpResponse response = CommonHttpClient.getHttpClient().execute(request)) {
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
|
||||
ClassicHttpRequest request = ClassicRequestBuilder.get()
|
||||
.setUri(metrics.getEureka_sd().getUrl() + APP_LIST_PATH)
|
||||
.build();
|
||||
|
||||
// Use ResponseHandler to avoid manual resource management and deprecated execute methods
|
||||
HttpClientResponseHandler<String> responseHandler = response -> {
|
||||
int statusCode = response.getCode();
|
||||
if (statusCode != 200) {
|
||||
log.warn("Failed to fetch eureka sd...");
|
||||
builder.setMsg("StatusCode " + statusCode);
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
String responseBody = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
|
||||
EurekaDiscoveryResponseEntity responseEntity =
|
||||
XmlUtil.fromXml(responseBody, EurekaDiscoveryResponseEntity.class);
|
||||
if (responseEntity == null || CollectionUtils.isEmpty(responseEntity.getApplications())) {
|
||||
return;
|
||||
return EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
|
||||
};
|
||||
|
||||
try {
|
||||
String responseBody = CommonHttpClient.getHttpClient().execute(request, responseHandler);
|
||||
|
||||
if (responseBody != null) {
|
||||
EurekaDiscoveryResponseEntity responseEntity =
|
||||
XmlUtil.fromXml(responseBody, EurekaDiscoveryResponseEntity.class);
|
||||
if (responseEntity == null || CollectionUtils.isEmpty(responseEntity.getApplications())) {
|
||||
return;
|
||||
}
|
||||
|
||||
responseEntity.getApplications()
|
||||
.stream()
|
||||
.filter(application -> !CollectionUtils.isEmpty(application.getInstances()))
|
||||
.forEach(application -> convertTarget(configList, application));
|
||||
|
||||
configList.forEach(config -> {
|
||||
CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
|
||||
valueRowBuilder.addColumn(config.getHost());
|
||||
valueRowBuilder.addColumn(config.getPort());
|
||||
builder.addValueRow(valueRowBuilder.build());
|
||||
});
|
||||
}
|
||||
|
||||
responseEntity.getApplications()
|
||||
.stream()
|
||||
.filter(application -> !CollectionUtils.isEmpty(application.getInstances()))
|
||||
.forEach(application -> convertTarget(configList, application));
|
||||
|
||||
configList.forEach(config -> {
|
||||
CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
|
||||
valueRowBuilder.addColumn(config.getHost());
|
||||
valueRowBuilder.addColumn(config.getPort());
|
||||
builder.addValueRow(valueRowBuilder.build());
|
||||
});
|
||||
} catch (IOException e) {
|
||||
String errorMsg = CommonUtil.getMessageFromThrowable(e);
|
||||
log.warn("Failed to fetch eureka sd... {}", errorMsg);
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg(errorMsg);
|
||||
} catch (Exception e) {
|
||||
String errorMsg = CommonUtil.getMessageFromThrowable(e);
|
||||
log.warn("Failed to fetch eureka sd... {}", errorMsg);
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg(errorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,4 +125,4 @@ public class EurekaSdCollectImpl extends AbstractCollect {
|
||||
public String supportProtocol() {
|
||||
return DispatchConstants.PROTOCOL_EUREKA_SD;
|
||||
}
|
||||
}
|
||||
}
|
||||
+81
-34
@@ -6,7 +6,7 @@
|
||||
* (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
|
||||
* 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,
|
||||
@@ -41,18 +41,18 @@ import org.apache.hertzbeat.common.entity.sd.ServiceDiscoveryResponseEntity;
|
||||
import org.apache.hertzbeat.common.util.Base64Util;
|
||||
import org.apache.hertzbeat.common.util.CommonUtil;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.apache.http.HttpHeaders;
|
||||
import org.apache.http.auth.AuthScope;
|
||||
import org.apache.http.auth.UsernamePasswordCredentials;
|
||||
import org.apache.http.client.CredentialsProvider;
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpUriRequest;
|
||||
import org.apache.http.client.methods.RequestBuilder;
|
||||
import org.apache.http.client.protocol.HttpClientContext;
|
||||
import org.apache.http.impl.client.BasicCredentialsProvider;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.apache.hc.core5.http.HttpHeaders;
|
||||
import org.apache.hc.core5.http.ClassicHttpRequest;
|
||||
import org.apache.hc.client5.http.auth.AuthScope;
|
||||
import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
|
||||
import org.apache.hc.client5.http.config.RequestConfig;
|
||||
import org.apache.hc.core5.http.io.HttpClientResponseHandler;
|
||||
import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
|
||||
import org.apache.hc.client5.http.protocol.HttpClientContext;
|
||||
import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
|
||||
import org.apache.hc.core5.http.protocol.HttpContext;
|
||||
import org.apache.hc.core5.http.io.entity.EntityUtils;
|
||||
import org.apache.hc.core5.util.Timeout;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.util.UriUtils;
|
||||
@@ -63,6 +63,28 @@ import org.springframework.web.util.UriUtils;
|
||||
*/
|
||||
@Slf4j
|
||||
public class HttpSdCollectImpl extends AbstractCollect {
|
||||
|
||||
/**
|
||||
* Inner class to wrap HTTP request with its configuration
|
||||
*/
|
||||
private static class HttpRequestWithConfig {
|
||||
private final ClassicHttpRequest request;
|
||||
private final RequestConfig requestConfig;
|
||||
|
||||
public HttpRequestWithConfig(ClassicHttpRequest request, RequestConfig requestConfig) {
|
||||
this.request = request;
|
||||
this.requestConfig = requestConfig;
|
||||
}
|
||||
|
||||
public ClassicHttpRequest getRequest() {
|
||||
return request;
|
||||
}
|
||||
|
||||
public RequestConfig getRequestConfig() {
|
||||
return requestConfig;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preCheck(Metrics metrics) throws IllegalArgumentException {
|
||||
}
|
||||
@@ -71,22 +93,30 @@ public class HttpSdCollectImpl extends AbstractCollect {
|
||||
public void collect(CollectRep.MetricsData.Builder builder, Metrics metrics) {
|
||||
List<ConnectionConfig> configList = Lists.newArrayList();
|
||||
|
||||
HttpUriRequest request = createHttpRequest(metrics.getHttp_sd());
|
||||
HttpContext httpContext = createHttpContext(metrics.getHttp_sd());
|
||||
try (CloseableHttpResponse response = CommonHttpClient.getHttpClient().execute(request, httpContext)) {
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
HttpRequestWithConfig requestWithConfig = createHttpRequest(metrics.getHttp_sd());
|
||||
if (requestWithConfig == null) {
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg("Failed to create HTTP request");
|
||||
return;
|
||||
}
|
||||
|
||||
ClassicHttpRequest request = requestWithConfig.getRequest();
|
||||
HttpContext httpContext = createHttpContext(metrics.getHttp_sd(), requestWithConfig.getRequestConfig());
|
||||
|
||||
HttpClientResponseHandler<Void> responseHandler = response -> {
|
||||
int statusCode = response.getCode();
|
||||
if (statusCode != 200) {
|
||||
log.warn("Failed to fetch sd...");
|
||||
builder.setMsg("StatusCode " + statusCode);
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
String responseBody = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
|
||||
TypeReference<List<ServiceDiscoveryResponseEntity>> typeReference = new TypeReference<>() {};
|
||||
final List<ServiceDiscoveryResponseEntity> responseEntityList = JsonUtil.fromJson(responseBody, typeReference);
|
||||
if (CollectionUtils.isEmpty(responseEntityList)) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
responseEntityList.stream()
|
||||
@@ -100,11 +130,21 @@ public class HttpSdCollectImpl extends AbstractCollect {
|
||||
valueRowBuilder.addColumn(config.getPort());
|
||||
builder.addValueRow(valueRowBuilder.build());
|
||||
});
|
||||
return null;
|
||||
};
|
||||
|
||||
try {
|
||||
CommonHttpClient.getHttpClient().execute(request, httpContext, responseHandler);
|
||||
} catch (IOException e) {
|
||||
String errorMsg = CommonUtil.getMessageFromThrowable(e);
|
||||
log.warn("Failed to fetch sd... {}", errorMsg);
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg(errorMsg);
|
||||
} catch (Exception e) {
|
||||
String errorMsg = CommonUtil.getMessageFromThrowable(e);
|
||||
log.warn("Failed to fetch sd... {}", errorMsg);
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg(errorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,17 +177,23 @@ public class HttpSdCollectImpl extends AbstractCollect {
|
||||
* create httpContext
|
||||
*
|
||||
* @param httpSdProtocol http sd protocol
|
||||
* @param requestConfig request config
|
||||
* @return context
|
||||
*/
|
||||
public HttpContext createHttpContext(HttpProtocol httpSdProtocol) {
|
||||
public HttpContext createHttpContext(HttpProtocol httpSdProtocol, RequestConfig requestConfig) {
|
||||
HttpClientContext clientContext = HttpClientContext.create();
|
||||
|
||||
if (requestConfig != null) {
|
||||
clientContext.setRequestConfig(requestConfig);
|
||||
}
|
||||
|
||||
HttpProtocol.Authorization auth = httpSdProtocol.getAuthorization();
|
||||
if (auth != null && DispatchConstants.DIGEST_AUTH.equals(auth.getType())) {
|
||||
HttpClientContext clientContext = new HttpClientContext();
|
||||
if (org.springframework.util.StringUtils.hasText(auth.getDigestAuthUsername())
|
||||
&& org.springframework.util.StringUtils.hasText(auth.getDigestAuthPassword())) {
|
||||
CredentialsProvider provider = new BasicCredentialsProvider();
|
||||
BasicCredentialsProvider provider = new BasicCredentialsProvider();
|
||||
UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(auth.getDigestAuthUsername(),
|
||||
auth.getDigestAuthPassword());
|
||||
auth.getDigestAuthPassword().toCharArray());
|
||||
URL url;
|
||||
try {
|
||||
url = new URL(httpSdProtocol.getUrl());
|
||||
@@ -159,20 +205,20 @@ public class HttpSdCollectImpl extends AbstractCollect {
|
||||
provider.setCredentials(authScope, credentials);
|
||||
|
||||
clientContext.setCredentialsProvider(provider);
|
||||
return clientContext;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return clientContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* create http request
|
||||
*
|
||||
* @param httpSdProtocol http request set
|
||||
* @return http uri request
|
||||
* @return http uri request wrapper
|
||||
*/
|
||||
public HttpUriRequest createHttpRequest(HttpProtocol httpSdProtocol) {
|
||||
RequestBuilder requestBuilder = RequestBuilder.get();
|
||||
@SuppressWarnings("deprecation")
|
||||
private HttpRequestWithConfig createHttpRequest(HttpProtocol httpSdProtocol) {
|
||||
ClassicRequestBuilder requestBuilder = ClassicRequestBuilder.get();
|
||||
|
||||
// The default request header can be overridden if customized
|
||||
// keep-alive
|
||||
@@ -227,16 +273,17 @@ public class HttpSdCollectImpl extends AbstractCollect {
|
||||
throw e;
|
||||
}
|
||||
|
||||
RequestConfig requestConfig = null;
|
||||
// custom timeout
|
||||
int timeout = CollectUtil.getTimeout(httpSdProtocol.getTimeout(), 0);
|
||||
if (timeout > 0) {
|
||||
RequestConfig requestConfig = RequestConfig.custom()
|
||||
.setConnectTimeout(timeout)
|
||||
.setSocketTimeout(timeout)
|
||||
requestConfig = RequestConfig.custom()
|
||||
.setConnectTimeout(Timeout.ofMilliseconds(timeout))
|
||||
.setResponseTimeout(Timeout.ofMilliseconds(timeout))
|
||||
.setRedirectsEnabled(true)
|
||||
.build();
|
||||
requestBuilder.setConfig(requestConfig);
|
||||
}
|
||||
return requestBuilder.build();
|
||||
|
||||
return new HttpRequestWithConfig(requestBuilder.build(), requestConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
-30
@@ -19,13 +19,10 @@ package org.apache.hertzbeat.collector.collect.redis;
|
||||
|
||||
import static org.apache.hertzbeat.common.constants.CommonConstants.TYPE_STRING;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import io.lettuce.core.RedisClient;
|
||||
import io.lettuce.core.RedisURI;
|
||||
import io.lettuce.core.api.StatefulRedisConnection;
|
||||
import io.lettuce.core.api.sync.RedisCommands;
|
||||
import io.lettuce.core.cluster.RedisClusterClient;
|
||||
import io.lettuce.core.cluster.api.StatefulRedisClusterConnection;
|
||||
import io.lettuce.core.cluster.api.sync.RedisAdvancedClusterCommands;
|
||||
import io.lettuce.core.cluster.models.partitions.Partitions;
|
||||
import io.lettuce.core.cluster.models.partitions.RedisClusterNode;
|
||||
import io.lettuce.core.resource.ClientResources;
|
||||
@@ -40,7 +37,6 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
@@ -55,19 +51,13 @@ public class RedisClusterCollectImplTest {
|
||||
|
||||
|
||||
@Mock
|
||||
private StatefulRedisClusterConnection<String, String> clusterConnection;
|
||||
private StatefulRedisClusterConnection<String, String> connection;
|
||||
|
||||
@Mock
|
||||
private StatefulRedisConnection<String, String> singleConnection;
|
||||
private RedisAdvancedClusterCommands<String, String> cmd;
|
||||
|
||||
@Mock
|
||||
private RedisCommands<String, String> cmd;
|
||||
|
||||
@Mock
|
||||
private RedisClusterClient clusterClient;
|
||||
|
||||
@Mock
|
||||
private RedisClient singleClient;
|
||||
private RedisClusterClient client;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
@@ -75,10 +65,8 @@ public class RedisClusterCollectImplTest {
|
||||
|
||||
@AfterEach
|
||||
void setDown() {
|
||||
clusterConnection.close();
|
||||
singleConnection.close();
|
||||
clusterClient.shutdown();
|
||||
singleClient.shutdown();
|
||||
connection.close();
|
||||
client.shutdown();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -122,14 +110,10 @@ public class RedisClusterCollectImplTest {
|
||||
metrics.setAliasFields(aliasField);
|
||||
metrics.setFields(fields);
|
||||
|
||||
MockedStatic<RedisClusterClient> redisClusterClientMockedStatic = Mockito.mockStatic(RedisClusterClient.class);
|
||||
redisClusterClientMockedStatic.when(() -> RedisClusterClient.create(Mockito.any(ClientResources.class),
|
||||
Mockito.any(RedisURI.class))).thenReturn(clusterClient);
|
||||
Mockito.when(clusterClient.connect()).thenReturn(clusterConnection);
|
||||
MockedStatic<RedisClient> redisClientMockedStatic = Mockito.mockStatic(RedisClient.class);
|
||||
redisClientMockedStatic.when(() -> RedisClient.create(Mockito.any(ClientResources.class),
|
||||
Mockito.any(RedisURI.class))).thenReturn(singleClient);
|
||||
Mockito.when(singleClient.connect()).thenReturn(singleConnection);
|
||||
|
||||
Mockito.mockStatic(RedisClusterClient.class).when(() -> RedisClusterClient.create(Mockito.any(ClientResources.class),
|
||||
Mockito.any(RedisURI.class))).thenReturn(client);
|
||||
Mockito.when(client.connect()).thenReturn(connection);
|
||||
|
||||
Partitions partitions = new Partitions();
|
||||
RedisClusterNode node = new RedisClusterNode();
|
||||
@@ -141,9 +125,9 @@ public class RedisClusterCollectImplTest {
|
||||
node2.setUri(RedisURI.create("redis://" + uri2));
|
||||
partitions.add(node2);
|
||||
|
||||
Mockito.when(clusterConnection.getPartitions()).thenReturn(partitions);
|
||||
Mockito.when(connection.getPartitions()).thenReturn(partitions);
|
||||
|
||||
Mockito.when(singleConnection.sync()).thenReturn(cmd);
|
||||
Mockito.when(connection.sync()).thenReturn(cmd);
|
||||
Mockito.when(cmd.info(metrics.getName())).thenReturn(info);
|
||||
Mockito.when(cmd.clusterInfo()).thenReturn(clusterInfo);
|
||||
|
||||
@@ -163,8 +147,6 @@ public class RedisClusterCollectImplTest {
|
||||
assertEquals(row.getColumns(2), uri2);
|
||||
}
|
||||
}
|
||||
redisClusterClientMockedStatic.close();
|
||||
redisClientMockedStatic.close();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -29,7 +29,7 @@ import org.apache.hertzbeat.collector.dispatch.CollectorInfoProperties;
|
||||
import org.apache.hertzbeat.collector.dispatch.DispatchProperties;
|
||||
import org.apache.hertzbeat.collector.dispatch.entrance.internal.CollectJobService;
|
||||
import org.apache.hertzbeat.collector.timer.TimerDispatch;
|
||||
import org.apache.hertzbeat.common.entity.message.ClusterMsg;
|
||||
import org.apache.hertzbeat.common.entity.message.ClusterMessage;
|
||||
import org.apache.hertzbeat.common.support.CommonThreadPool;
|
||||
import org.apache.hertzbeat.remoting.RemotingClient;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -113,7 +113,7 @@ class CollectServerTest {
|
||||
|
||||
RemotingClient remotingClient = mock(RemotingClient.class);
|
||||
ReflectionTestUtils.setField(collectServer, "remotingClient", remotingClient);
|
||||
ClusterMsg.Message message = mock(ClusterMsg.Message.class);
|
||||
ClusterMessage message = mock(ClusterMessage.class);
|
||||
|
||||
collectServer.sendMsg(message);
|
||||
|
||||
@@ -135,7 +135,7 @@ class CollectServerTest {
|
||||
collectNettyEventListener.onChannelActive(channel);
|
||||
|
||||
verify(timerDispatch, times(1)).goOnline();
|
||||
verify(remotingClient, times(1)).sendMsg(any(ClusterMsg.Message.class));
|
||||
verify(remotingClient, times(1)).sendMsg(any(ClusterMessage.class));
|
||||
|
||||
ScheduledExecutorService scheduledExecutor =
|
||||
(ScheduledExecutorService) ReflectionTestUtils.getField(collectServer, "scheduledExecutor");
|
||||
|
||||
+3
-3
@@ -34,7 +34,7 @@ import org.apache.hertzbeat.collector.dispatch.WorkerPool;
|
||||
import org.apache.hertzbeat.collector.dispatch.entrance.CollectServer;
|
||||
import org.apache.hertzbeat.collector.timer.TimerDispatch;
|
||||
import org.apache.hertzbeat.common.entity.job.Job;
|
||||
import org.apache.hertzbeat.common.entity.message.ClusterMsg;
|
||||
import org.apache.hertzbeat.common.entity.message.ClusterMessage;
|
||||
import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -116,7 +116,7 @@ class CollectJobServiceTest {
|
||||
|
||||
collectJobService.collectSyncOneTimeJobData(job);
|
||||
|
||||
verify(collectServer, times(1)).sendMsg(any(ClusterMsg.Message.class));
|
||||
verify(collectServer, times(1)).sendMsg(any(ClusterMessage.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -137,7 +137,7 @@ class CollectJobServiceTest {
|
||||
.build();
|
||||
collectJobService.sendAsyncCollectData(metricsData);
|
||||
|
||||
verify(collectServer, times(1)).sendMsg(any(ClusterMsg.Message.class));
|
||||
verify(collectServer, times(1)).sendMsg(any(ClusterMessage.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -100,6 +100,35 @@
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>dev-driver</id>
|
||||
<activation>
|
||||
<activeByDefault>true</activeByDefault>
|
||||
</activation>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.oracle.database.jdbc</groupId>
|
||||
<artifactId>ojdbc8</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.oracle.database.nls</groupId>
|
||||
<artifactId>orai18n</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.ibm.db2</groupId>
|
||||
<artifactId>jcc</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
<id>inner</id>
|
||||
<build>
|
||||
|
||||
@@ -28,8 +28,6 @@ spring:
|
||||
jackson:
|
||||
default-property-inclusion: ALWAYS
|
||||
# need to disable spring boot mongodb auto config, or default mongodb connection tried and failed...
|
||||
autoconfigure:
|
||||
exclude: org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration, org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration, org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
|
||||
+6
-45
@@ -1,27 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
<configuration scan="true">
|
||||
<springProperty scope="context" name="application_name" source="spring.application.name" defaultValue="collector"/>
|
||||
<!-- Output logs to ConsoleAppender -->
|
||||
|
||||
<appender name="CONSOLE_RAW" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<!--<pattern>%d %p (%file:%line\)- %m%n</pattern>-->
|
||||
<!-- Format output: %d for date, %thread for thread name, %-5level for level with 5 character width, %msg for log message, %n for new line -->
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger - %msg%n</pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
@@ -35,23 +17,14 @@
|
||||
</appender>
|
||||
|
||||
<appender name="SystemOutFileAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<!-- Rolling policy for loggers, by date and by size -->
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<!-- Archive log file path. %d{yyyy-MM-dd} specifies date format, %i specifies index -->
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<fileNamePattern>logs/${application_name}-%d{yyyy-MM-dd}.%i.log.zip</fileNamePattern>
|
||||
<!-- Log retention duration -->
|
||||
<maxFileSize>50MB</maxFileSize>
|
||||
<maxHistory>7</maxHistory>
|
||||
<!-- Maximum size of log retention -->
|
||||
<totalSizeCap>5GB</totalSizeCap>
|
||||
<cleanHistoryOnStart>true</cleanHistoryOnStart>
|
||||
<!-- Besides logging by day, log files cannot exceed 200M, if exceeded, log files will start from index 0 -->
|
||||
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
|
||||
<maxFileSize>50MB</maxFileSize>
|
||||
</timeBasedFileNamingAndTriggeringPolicy>
|
||||
</rollingPolicy>
|
||||
<!-- Append logs -->
|
||||
<append>true</append>
|
||||
<!-- Log file format -->
|
||||
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger Line:%-3L - %msg%n</pattern>
|
||||
<charset>utf-8</charset>
|
||||
@@ -59,32 +32,23 @@
|
||||
</appender>
|
||||
|
||||
<appender name="ErrOutFileAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<fileNamePattern>logs/${application_name}-%d{yyyy-MM-dd}-error.%i.log.zip</fileNamePattern>
|
||||
<!-- Log retention duration -->
|
||||
<maxFileSize>50MB</maxFileSize>
|
||||
<maxHistory>7</maxHistory>
|
||||
<!-- Maximum size of log retention -->
|
||||
<totalSizeCap>5GB</totalSizeCap>
|
||||
<cleanHistoryOnStart>true</cleanHistoryOnStart>
|
||||
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
|
||||
<maxFileSize>50MB</maxFileSize>
|
||||
</timeBasedFileNamingAndTriggeringPolicy>
|
||||
</rollingPolicy>
|
||||
<!-- Append logs -->
|
||||
<append>true</append>
|
||||
<!-- Log file format -->
|
||||
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger Line:%-3L - %msg%n</pattern>
|
||||
<charset>utf-8</charset>
|
||||
</encoder>
|
||||
<!-- This log file records error and above levels -->
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||
<level>ERROR</level>
|
||||
</filter>
|
||||
</appender>
|
||||
|
||||
<!-- Settings for this logger: for example, all output logs under the org.springframework package must be at level info or above to be output! -->
|
||||
<!-- This can avoid outputting many common debug information of the spring framework! -->
|
||||
<logger name="org.springframework" level="info" />
|
||||
<logger name="org.json" level="error"/>
|
||||
<logger name="io.netty" level="info"/>
|
||||
@@ -98,7 +62,6 @@
|
||||
<logger name="org.mongodb" level="warn"/>
|
||||
<logger name="io.greptime" level="warn"/>
|
||||
|
||||
<!-- Production environment configuration -->
|
||||
<springProfile name="prod">
|
||||
<root level="INFO">
|
||||
<appender-ref ref="ConsoleAppender"/>
|
||||
@@ -115,7 +78,6 @@
|
||||
</root>
|
||||
</springProfile>
|
||||
|
||||
<!-- Development environment configuration -->
|
||||
<springProfile name="dev">
|
||||
<root level="INFO">
|
||||
<appender-ref ref="ConsoleAppender"/>
|
||||
@@ -124,7 +86,6 @@
|
||||
</root>
|
||||
</springProfile>
|
||||
|
||||
<!-- Development environment configuration -->
|
||||
<springProfile name="mysql">
|
||||
<root level="INFO">
|
||||
<appender-ref ref="ConsoleAppender"/>
|
||||
@@ -133,4 +94,4 @@
|
||||
</root>
|
||||
</springProfile>
|
||||
|
||||
</configuration>
|
||||
</configuration>
|
||||
+56
-51
@@ -6,7 +6,7 @@
|
||||
* (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
|
||||
* 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,
|
||||
@@ -17,33 +17,30 @@
|
||||
|
||||
package org.apache.hertzbeat.collector.collect.common.http;
|
||||
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateExpiredException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.config.Registry;
|
||||
import org.apache.http.config.RegistryBuilder;
|
||||
import org.apache.http.conn.socket.ConnectionSocketFactory;
|
||||
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
|
||||
import org.apache.http.conn.ssl.NoopHostnameVerifier;
|
||||
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
|
||||
import org.apache.http.ssl.SSLContexts;
|
||||
import org.apache.hc.client5.http.config.ConnectionConfig;
|
||||
import org.apache.hc.client5.http.config.RequestConfig;
|
||||
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
|
||||
import org.apache.hc.client5.http.impl.classic.HttpClients;
|
||||
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
|
||||
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
|
||||
import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
|
||||
import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
|
||||
import org.apache.hc.core5.reactor.ssl.SSLBufferMode;
|
||||
import org.apache.hc.core5.ssl.SSLContexts;
|
||||
import org.apache.hc.core5.util.TimeValue;
|
||||
import org.apache.hc.core5.util.Timeout;
|
||||
|
||||
/**
|
||||
* common http client
|
||||
* common http client for HttpComponents Client 5.x
|
||||
*/
|
||||
@Slf4j
|
||||
public class CommonHttpClient {
|
||||
@@ -84,11 +81,13 @@ public class CommonHttpClient {
|
||||
|
||||
/**
|
||||
* ssl supported version
|
||||
* Note: SSLv3 is often disabled in modern JDKs
|
||||
*/
|
||||
private static final String[] SUPPORTED_SSL = {"TLSv1", "TLSv1.1", "TLSv1.2", "SSLv3"};
|
||||
|
||||
static {
|
||||
try {
|
||||
// 1. SSL Context Configuration
|
||||
SSLContext sslContext = SSLContexts.createDefault();
|
||||
X509TrustManager x509TrustManager = new X509TrustManager() {
|
||||
@Override
|
||||
@@ -102,7 +101,7 @@ public class CommonHttpClient {
|
||||
for (X509Certificate certificate : x509Certificates) {
|
||||
Date deadline = certificate.getNotAfter();
|
||||
if (deadline != null && now.after(deadline)) {
|
||||
throw new CertificateExpiredException();
|
||||
throw new CertificateExpiredException("Server certificate expired at " + deadline);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -112,57 +111,63 @@ public class CommonHttpClient {
|
||||
public X509Certificate[] getAcceptedIssuers() { return null; }
|
||||
};
|
||||
sslContext.init(null, new TrustManager[]{x509TrustManager}, null);
|
||||
SSLConnectionSocketFactory sslFactory = new SSLConnectionSocketFactory(sslContext, SUPPORTED_SSL, null, new NoopHostnameVerifier());
|
||||
Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
|
||||
.register("http", PlainConnectionSocketFactory.INSTANCE)
|
||||
.register("https", sslFactory)
|
||||
|
||||
// 2. TlsStrategy Configuration (Replaces deprecated SSLConnectionSocketFactory/Registry)
|
||||
DefaultClientTlsStrategy tlsStrategy = new DefaultClientTlsStrategy(
|
||||
sslContext,
|
||||
SUPPORTED_SSL,
|
||||
null, // Supported cipher suites (null = default)
|
||||
SSLBufferMode.STATIC,
|
||||
NoopHostnameVerifier.INSTANCE
|
||||
);
|
||||
|
||||
// 3. Connection Manager Configuration (Using Builder)
|
||||
ConnectionConfig connectionConfig = ConnectionConfig.custom()
|
||||
.setConnectTimeout(Timeout.ofMilliseconds(CONNECT_TIMEOUT))
|
||||
.setSocketTimeout(Timeout.ofMilliseconds(SOCKET_TIMEOUT))
|
||||
.setValidateAfterInactivity(TimeValue.ofMilliseconds(INACTIVITY_VALIDATED_TIME))
|
||||
.build();
|
||||
|
||||
connectionManager = PoolingHttpClientConnectionManagerBuilder.create()
|
||||
.setTlsSocketStrategy(tlsStrategy)
|
||||
.setMaxConnTotal(MAX_TOTAL_CONNECTIONS)
|
||||
.setMaxConnPerRoute(MAX_PER_ROUTE_CONNECTIONS)
|
||||
.setDefaultConnectionConfig(connectionConfig)
|
||||
.build();
|
||||
|
||||
// 4. Request Config (Connection Request Timeout & Redirects)
|
||||
RequestConfig requestConfig = RequestConfig.custom()
|
||||
.setConnectionRequestTimeout(REQUIRE_CONNECT_TIMEOUT)
|
||||
.setConnectTimeout(CONNECT_TIMEOUT)
|
||||
.setSocketTimeout(SOCKET_TIMEOUT)
|
||||
// auto redirect when 301 302 response status
|
||||
.setConnectionRequestTimeout(Timeout.ofMilliseconds(REQUIRE_CONNECT_TIMEOUT))
|
||||
.setRedirectsEnabled(true)
|
||||
.build();
|
||||
// connection pool
|
||||
connectionManager = new PoolingHttpClientConnectionManager(registry);
|
||||
connectionManager.setMaxTotal(MAX_TOTAL_CONNECTIONS);
|
||||
connectionManager.setDefaultMaxPerRoute(MAX_PER_ROUTE_CONNECTIONS);
|
||||
connectionManager.setValidateAfterInactivity(INACTIVITY_VALIDATED_TIME);
|
||||
|
||||
// 5. Build HttpClient
|
||||
httpClient = HttpClients.custom()
|
||||
.setConnectionManager(connectionManager)
|
||||
.setDefaultRequestConfig(requestConfig)
|
||||
// clean up unavailable expired connections
|
||||
.evictExpiredConnections()
|
||||
// clean up available but idle connections
|
||||
.evictIdleConnections(100, TimeUnit.SECONDS)
|
||||
.evictIdleConnections(TimeValue.ofSeconds(100))
|
||||
.build();
|
||||
ThreadFactory threadFactory = new ThreadFactoryBuilder()
|
||||
.setNameFormat("http-connection-pool-cleaner-%d")
|
||||
.setDaemon(true)
|
||||
.build();
|
||||
ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(1, threadFactory);
|
||||
scheduledExecutor.scheduleWithFixedDelay(() -> {
|
||||
connectionManager.closeExpiredConnections();
|
||||
connectionManager.closeIdleConnections(40, TimeUnit.SECONDS);
|
||||
}, 40L, 40L, TimeUnit.SECONDS);
|
||||
|
||||
// shutdown hook
|
||||
// Shutdown hook
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(CommonHttpClient::close));
|
||||
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Initialize CommonHttpClient error", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static CloseableHttpClient getHttpClient() {
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
|
||||
public static void close() {
|
||||
try {
|
||||
httpClient.close();
|
||||
if (httpClient != null) {
|
||||
httpClient.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("close http client error", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
-24
@@ -49,28 +49,4 @@ public interface CollectorConstants extends NetworkConstants {
|
||||
|
||||
String STATUS_CODE = "statusCode";
|
||||
|
||||
/**
|
||||
* Maximum XML response size in bytes (10MB) to prevent DoS attacks
|
||||
*/
|
||||
int MAX_XML_RESPONSE_SIZE = 10 * 1024 * 1024;
|
||||
|
||||
/**
|
||||
* Maximum number of nodes returned by Xpath query to prevent excessive resource consumption
|
||||
*/
|
||||
int MAX_XPATH_RESULT_NODES = 1000;
|
||||
|
||||
/**
|
||||
* Dangerous Xpath expression patterns that could cause DoS attacks
|
||||
* These patterns match expressions that traverse the entire XML document
|
||||
*/
|
||||
String[] DANGEROUS_XPATH_PATTERNS = {
|
||||
"//\\*\\s*\\|\\s*//@\\*\\s*\\|\\s*//text\\(\\)", // //* | //@* | //text()
|
||||
"//\\*\\s*\\|", // //* | ...
|
||||
"//@\\*\\s*\\|", // //@* | ...
|
||||
"//node\\(\\)\\s*\\|", // //node() | ...
|
||||
"descendant-or-self::node\\(\\)\\s*\\|", // descendant-or-self::node() | ...
|
||||
"/descendant-or-self::node\\(\\)", // /descendant-or-self::node()
|
||||
"//\\*[\\s\\S]*//\\*" // //** with multiple wildcards
|
||||
};
|
||||
|
||||
}
|
||||
+16
-33
@@ -17,65 +17,48 @@
|
||||
|
||||
package org.apache.hertzbeat.collector.dispatch;
|
||||
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Collection task worker thread pool
|
||||
* Collection task worker thread pool with Virtual Threads
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class WorkerPool implements DisposableBean {
|
||||
|
||||
private ThreadPoolExecutor workerExecutor;
|
||||
private ExecutorService workerExecutor;
|
||||
|
||||
public WorkerPool() {
|
||||
initWorkExecutor();
|
||||
}
|
||||
|
||||
private void initWorkExecutor() {
|
||||
// thread factory
|
||||
ThreadFactory threadFactory = new ThreadFactoryBuilder()
|
||||
.setUncaughtExceptionHandler((thread, throwable) -> {
|
||||
log.error("[Important] WorkerPool workerExecutor has uncaughtException.", throwable);
|
||||
log.error("Thread Name {} : {}", thread.getName(), throwable.getMessage(), throwable);
|
||||
})
|
||||
.setDaemon(true)
|
||||
.setNameFormat("collect-worker-%d")
|
||||
.build();
|
||||
int coreSize = Math.max(2, Runtime.getRuntime().availableProcessors());
|
||||
int maxSize = Runtime.getRuntime().availableProcessors() * 16;
|
||||
workerExecutor = new ThreadPoolExecutor(coreSize,
|
||||
maxSize,
|
||||
10,
|
||||
TimeUnit.SECONDS,
|
||||
new SynchronousQueue<>(),
|
||||
threadFactory,
|
||||
new ThreadPoolExecutor.AbortPolicy());
|
||||
ThreadFactory virtualThreadFactory = Thread.ofVirtual()
|
||||
.name("collect-vt-", 0)
|
||||
.factory();
|
||||
|
||||
workerExecutor = Executors.newThreadPerTaskExecutor(virtualThreadFactory);
|
||||
log.info("WorkerPool initialized with JDK 25 Virtual Threads successfully.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the collection task thread
|
||||
*
|
||||
* @param runnable Task
|
||||
* @throws RejectedExecutionException when thread pool full
|
||||
* Run the collection task
|
||||
* @param runnable Task
|
||||
*/
|
||||
public void executeJob(Runnable runnable) throws RejectedExecutionException {
|
||||
public void executeJob(Runnable runnable) {
|
||||
workerExecutor.execute(runnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
public void destroy() {
|
||||
if (workerExecutor != null) {
|
||||
workerExecutor.shutdownNow();
|
||||
workerExecutor.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
-22
@@ -6,7 +6,7 @@
|
||||
* (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
|
||||
* 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,
|
||||
@@ -18,7 +18,6 @@
|
||||
package org.apache.hertzbeat.collector.dispatch.entrance;
|
||||
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import com.google.protobuf.ByteString;
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.collector.dispatch.CollectorInfoProperties;
|
||||
@@ -33,7 +32,7 @@ import org.apache.hertzbeat.collector.dispatch.entrance.processor.GoOnlineProces
|
||||
import org.apache.hertzbeat.collector.dispatch.entrance.processor.HeartbeatProcessor;
|
||||
import org.apache.hertzbeat.collector.timer.TimerDispatch;
|
||||
import org.apache.hertzbeat.common.entity.dto.CollectorInfo;
|
||||
import org.apache.hertzbeat.common.entity.message.ClusterMsg;
|
||||
import org.apache.hertzbeat.common.entity.message.ClusterMessage;
|
||||
import org.apache.hertzbeat.common.support.CommonThreadPool;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.apache.hertzbeat.remoting.RemotingClient;
|
||||
@@ -65,7 +64,7 @@ public class CollectServer implements CommandLineRunner {
|
||||
private final TimerDispatch timerDispatch;
|
||||
|
||||
private final CollectorInfoProperties infoProperties;
|
||||
|
||||
|
||||
private RemotingClient remotingClient;
|
||||
|
||||
private ScheduledExecutorService scheduledExecutor;
|
||||
@@ -97,13 +96,13 @@ public class CollectServer implements CommandLineRunner {
|
||||
nettyClientConfig.setServerPort(nettyProperties.getManagerPort());
|
||||
this.remotingClient = new NettyRemotingClient(nettyClientConfig, new CollectNettyEventListener(), threadPool);
|
||||
|
||||
this.remotingClient.registerProcessor(ClusterMsg.MessageType.HEARTBEAT, new HeartbeatProcessor());
|
||||
this.remotingClient.registerProcessor(ClusterMsg.MessageType.ISSUE_CYCLIC_TASK, new CollectCyclicDataProcessor(this));
|
||||
this.remotingClient.registerProcessor(ClusterMsg.MessageType.DELETE_CYCLIC_TASK, new DeleteCyclicTaskProcessor(this));
|
||||
this.remotingClient.registerProcessor(ClusterMsg.MessageType.ISSUE_ONE_TIME_TASK, new CollectOneTimeDataProcessor(this));
|
||||
this.remotingClient.registerProcessor(ClusterMsg.MessageType.GO_OFFLINE, new GoOfflineProcessor());
|
||||
this.remotingClient.registerProcessor(ClusterMsg.MessageType.GO_ONLINE, new GoOnlineProcessor());
|
||||
this.remotingClient.registerProcessor(ClusterMsg.MessageType.GO_CLOSE, new GoCloseProcessor(this));
|
||||
this.remotingClient.registerProcessor(ClusterMessage.MessageType.HEARTBEAT, new HeartbeatProcessor());
|
||||
this.remotingClient.registerProcessor(ClusterMessage.MessageType.ISSUE_CYCLIC_TASK, new CollectCyclicDataProcessor(this));
|
||||
this.remotingClient.registerProcessor(ClusterMessage.MessageType.DELETE_CYCLIC_TASK, new DeleteCyclicTaskProcessor(this));
|
||||
this.remotingClient.registerProcessor(ClusterMessage.MessageType.ISSUE_ONE_TIME_TASK, new CollectOneTimeDataProcessor(this));
|
||||
this.remotingClient.registerProcessor(ClusterMessage.MessageType.GO_OFFLINE, new GoOfflineProcessor());
|
||||
this.remotingClient.registerProcessor(ClusterMessage.MessageType.GO_ONLINE, new GoOnlineProcessor());
|
||||
this.remotingClient.registerProcessor(ClusterMessage.MessageType.GO_CLOSE, new GoCloseProcessor(this));
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
@@ -116,7 +115,7 @@ public class CollectServer implements CommandLineRunner {
|
||||
return collectJobService;
|
||||
}
|
||||
|
||||
public void sendMsg(final ClusterMsg.Message message) {
|
||||
public void sendMsg(final ClusterMessage message) {
|
||||
this.remotingClient.sendMsg(message);
|
||||
}
|
||||
|
||||
@@ -143,10 +142,10 @@ public class CollectServer implements CommandLineRunner {
|
||||
.build();
|
||||
timerDispatch.goOnline();
|
||||
// send online message
|
||||
ClusterMsg.Message message = ClusterMsg.Message.newBuilder()
|
||||
.setIdentity(identity)
|
||||
.setType(ClusterMsg.MessageType.GO_ONLINE)
|
||||
.setMsg(ByteString.copyFromUtf8(JsonUtil.toJson(collectorInfo)))
|
||||
ClusterMessage message = ClusterMessage.builder()
|
||||
.identity(identity)
|
||||
.type(ClusterMessage.MessageType.GO_ONLINE)
|
||||
.msg(JsonUtil.toJsonBytes(collectorInfo))
|
||||
.build();
|
||||
CollectServer.this.sendMsg(message);
|
||||
|
||||
@@ -163,13 +162,13 @@ public class CollectServer implements CommandLineRunner {
|
||||
// schedule send heartbeat message
|
||||
scheduledExecutor.scheduleAtFixedRate(() -> {
|
||||
try {
|
||||
ClusterMsg.Message heartbeat = ClusterMsg.Message.newBuilder()
|
||||
.setIdentity(identity)
|
||||
.setDirection(ClusterMsg.Direction.REQUEST)
|
||||
.setType(ClusterMsg.MessageType.HEARTBEAT)
|
||||
ClusterMessage heartbeat = ClusterMessage.builder()
|
||||
.identity(identity)
|
||||
.direction(ClusterMessage.Direction.REQUEST)
|
||||
.type(ClusterMessage.MessageType.HEARTBEAT)
|
||||
.build();
|
||||
CollectServer.this.sendMsg(heartbeat);
|
||||
log.info("collector send cluster server heartbeat, time: {}.", System.currentTimeMillis());
|
||||
log.info("collector send cluster server heartbeat, time: {}.", System.currentTimeMillis());
|
||||
} catch (Exception e) {
|
||||
log.error("schedule send heartbeat to server error.{}", e.getMessage());
|
||||
}
|
||||
@@ -182,4 +181,4 @@ public class CollectServer implements CommandLineRunner {
|
||||
log.info("handle idle event triggered. collector is going offline.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+17
-18
@@ -6,7 +6,7 @@
|
||||
* (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
|
||||
* 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,
|
||||
@@ -17,7 +17,6 @@
|
||||
|
||||
package org.apache.hertzbeat.collector.dispatch.entrance.internal;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.collector.dispatch.DispatchProperties;
|
||||
import org.apache.hertzbeat.collector.dispatch.WorkerPool;
|
||||
@@ -25,7 +24,7 @@ import org.apache.hertzbeat.collector.dispatch.entrance.CollectServer;
|
||||
import org.apache.hertzbeat.collector.timer.TimerDispatch;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.job.Job;
|
||||
import org.apache.hertzbeat.common.entity.message.ClusterMsg;
|
||||
import org.apache.hertzbeat.common.entity.message.ClusterMessage;
|
||||
import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
import org.apache.hertzbeat.common.util.ArrowUtil;
|
||||
import org.apache.hertzbeat.common.util.IpDomainUtil;
|
||||
@@ -117,10 +116,10 @@ public class CollectJobService {
|
||||
workerPool.executeJob(() -> {
|
||||
List<CollectRep.MetricsData> metricsDataList = this.collectSyncJobData(oneTimeJob);
|
||||
byte[] msg = ArrowUtil.serializeMetricsData(metricsDataList);
|
||||
ClusterMsg.Message message = ClusterMsg.Message.newBuilder()
|
||||
.setMsg(ByteString.copyFrom(msg))
|
||||
.setDirection(ClusterMsg.Direction.REQUEST)
|
||||
.setType(ClusterMsg.MessageType.RESPONSE_ONE_TIME_TASK_DATA)
|
||||
ClusterMessage message = ClusterMessage.builder()
|
||||
.msg(msg)
|
||||
.direction(ClusterMessage.Direction.REQUEST)
|
||||
.type(ClusterMessage.MessageType.RESPONSE_ONE_TIME_TASK_DATA)
|
||||
.build();
|
||||
this.collectServer.sendMsg(message);
|
||||
});
|
||||
@@ -153,22 +152,22 @@ public class CollectJobService {
|
||||
*/
|
||||
public void sendAsyncCollectData(CollectRep.MetricsData metricsData) {
|
||||
byte[] msg = ArrowUtil.serializeMetricsData(List.of(metricsData));
|
||||
ClusterMsg.Message message = ClusterMsg.Message.newBuilder()
|
||||
.setIdentity(collectorIdentity)
|
||||
.setMsg(ByteString.copyFrom(msg))
|
||||
.setDirection(ClusterMsg.Direction.REQUEST)
|
||||
.setType(ClusterMsg.MessageType.RESPONSE_CYCLIC_TASK_DATA)
|
||||
ClusterMessage message = ClusterMessage.builder()
|
||||
.identity(collectorIdentity)
|
||||
.msg(msg)
|
||||
.direction(ClusterMessage.Direction.REQUEST)
|
||||
.type(ClusterMessage.MessageType.RESPONSE_CYCLIC_TASK_DATA)
|
||||
.build();
|
||||
this.collectServer.sendMsg(message);
|
||||
}
|
||||
|
||||
public void sendAsyncServiceDiscoveryData(CollectRep.MetricsData metricsData) {
|
||||
byte[] msg = ArrowUtil.serializeMetricsData(List.of(metricsData));
|
||||
ClusterMsg.Message message = ClusterMsg.Message.newBuilder()
|
||||
.setIdentity(collectorIdentity)
|
||||
.setMsg(ByteString.copyFrom(msg))
|
||||
.setDirection(ClusterMsg.Direction.REQUEST)
|
||||
.setType(ClusterMsg.MessageType.RESPONSE_CYCLIC_TASK_SD_DATA)
|
||||
ClusterMessage message = ClusterMessage.builder()
|
||||
.identity(collectorIdentity)
|
||||
.msg(msg)
|
||||
.direction(ClusterMessage.Direction.REQUEST)
|
||||
.type(ClusterMessage.MessageType.RESPONSE_CYCLIC_TASK_SD_DATA)
|
||||
.build();
|
||||
this.collectServer.sendMsg(message);
|
||||
}
|
||||
@@ -184,4 +183,4 @@ public class CollectJobService {
|
||||
public void setCollectServer(CollectServer collectServer) {
|
||||
this.collectServer = collectServer;
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -21,7 +21,7 @@ import io.netty.channel.ChannelHandlerContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.collector.dispatch.entrance.CollectServer;
|
||||
import org.apache.hertzbeat.common.entity.job.Job;
|
||||
import org.apache.hertzbeat.common.entity.message.ClusterMsg;
|
||||
import org.apache.hertzbeat.common.entity.message.ClusterMessage;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.apache.hertzbeat.remoting.netty.NettyRemotingProcessor;
|
||||
|
||||
@@ -37,8 +37,8 @@ public class CollectCyclicDataProcessor implements NettyRemotingProcessor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClusterMsg.Message handle(ChannelHandlerContext ctx, ClusterMsg.Message message) {
|
||||
Job job = JsonUtil.fromJson(message.getMsg().toStringUtf8(), Job.class);
|
||||
public ClusterMessage handle(ChannelHandlerContext ctx, ClusterMessage message) {
|
||||
Job job = JsonUtil.fromJson(message.getMsg(), Job.class);
|
||||
if (job == null) {
|
||||
log.error("collector receive cyclic task job is null");
|
||||
return null;
|
||||
|
||||
+3
-3
@@ -20,7 +20,7 @@ package org.apache.hertzbeat.collector.dispatch.entrance.processor;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import org.apache.hertzbeat.collector.dispatch.entrance.CollectServer;
|
||||
import org.apache.hertzbeat.common.entity.job.Job;
|
||||
import org.apache.hertzbeat.common.entity.message.ClusterMsg;
|
||||
import org.apache.hertzbeat.common.entity.message.ClusterMessage;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.apache.hertzbeat.remoting.netty.NettyRemotingProcessor;
|
||||
|
||||
@@ -35,8 +35,8 @@ public class CollectOneTimeDataProcessor implements NettyRemotingProcessor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClusterMsg.Message handle(ChannelHandlerContext ctx, ClusterMsg.Message message) {
|
||||
Job oneTimeJob = JsonUtil.fromJson(message.getMsg().toStringUtf8(), Job.class);
|
||||
public ClusterMessage handle(ChannelHandlerContext ctx, ClusterMessage message) {
|
||||
Job oneTimeJob = JsonUtil.fromJson(message.getMsg(), Job.class);
|
||||
collectServer.getCollectJobService().collectSyncOneTimeJobData(oneTimeJob);
|
||||
return null;
|
||||
}
|
||||
|
||||
+3
-3
@@ -21,7 +21,7 @@ import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.collector.dispatch.entrance.CollectServer;
|
||||
import org.apache.hertzbeat.common.entity.message.ClusterMsg;
|
||||
import org.apache.hertzbeat.common.entity.message.ClusterMessage;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.apache.hertzbeat.remoting.netty.NettyRemotingProcessor;
|
||||
|
||||
@@ -39,9 +39,9 @@ public class DeleteCyclicTaskProcessor implements NettyRemotingProcessor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClusterMsg.Message handle(ChannelHandlerContext ctx, ClusterMsg.Message message) {
|
||||
public ClusterMessage handle(ChannelHandlerContext ctx, ClusterMessage message) {
|
||||
TypeReference<List<Long>> typeReference = new TypeReference<>() {};
|
||||
List<Long> jobIds = JsonUtil.fromJson(message.getMsg().toStringUtf8(), typeReference);
|
||||
List<Long> jobIds = JsonUtil.fromJson(message.getMsgString(), typeReference);
|
||||
if (jobIds == null || jobIds.isEmpty()) {
|
||||
log.error("collector receive delete cyclic task job ids is null");
|
||||
return null;
|
||||
|
||||
+4
-4
@@ -22,7 +22,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.collector.dispatch.entrance.CollectServer;
|
||||
import org.apache.hertzbeat.collector.timer.TimerDispatch;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.message.ClusterMsg;
|
||||
import org.apache.hertzbeat.common.entity.message.ClusterMessage;
|
||||
import org.apache.hertzbeat.common.support.SpringContextHolder;
|
||||
import org.apache.hertzbeat.remoting.netty.NettyRemotingProcessor;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
@@ -41,12 +41,12 @@ public class GoCloseProcessor implements NettyRemotingProcessor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClusterMsg.Message handle(ChannelHandlerContext ctx, ClusterMsg.Message message) {
|
||||
public ClusterMessage handle(ChannelHandlerContext ctx, ClusterMessage message) {
|
||||
if (this.timerDispatch == null) {
|
||||
this.timerDispatch = SpringContextHolder.getBean(TimerDispatch.class);
|
||||
}
|
||||
if (message.getMsg().toStringUtf8().contains(CommonConstants.COLLECTOR_AUTH_FAILED)) {
|
||||
log.error("[Auth Failed]receive client auth failed message and go close. {}", message.getMsg());
|
||||
if (message.getMsgString().contains(CommonConstants.COLLECTOR_AUTH_FAILED)) {
|
||||
log.error("[Auth Failed]receive client auth failed message and go close. {}", message.getMsgString());
|
||||
}
|
||||
this.timerDispatch.goOffline();
|
||||
this.collectServer.shutdown();
|
||||
|
||||
+10
-9
@@ -17,12 +17,12 @@
|
||||
|
||||
package org.apache.hertzbeat.collector.dispatch.entrance.processor;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.collector.timer.TimerDispatch;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.message.ClusterMsg;
|
||||
import org.apache.hertzbeat.common.entity.message.ClusterMessage;
|
||||
import org.apache.hertzbeat.common.support.SpringContextHolder;
|
||||
import org.apache.hertzbeat.remoting.netty.NettyRemotingProcessor;
|
||||
|
||||
@@ -36,20 +36,21 @@ public class GoOfflineProcessor implements NettyRemotingProcessor {
|
||||
private TimerDispatch timerDispatch;
|
||||
|
||||
@Override
|
||||
public ClusterMsg.Message handle(ChannelHandlerContext ctx, ClusterMsg.Message message) {
|
||||
public ClusterMessage handle(ChannelHandlerContext ctx, ClusterMessage message) {
|
||||
if (this.timerDispatch == null) {
|
||||
this.timerDispatch = SpringContextHolder.getBean(TimerDispatch.class);
|
||||
}
|
||||
timerDispatch.goOffline();
|
||||
log.info("receive offline message and handle success");
|
||||
if (message.getMsg().toStringUtf8().contains(CommonConstants.COLLECTOR_AUTH_FAILED)) {
|
||||
log.error("[Auth Failed]receive client auth failed message and go offline. {}", message.getMsg());
|
||||
if (message.getMsgString().contains(CommonConstants.COLLECTOR_AUTH_FAILED)) {
|
||||
log.error("[Auth Failed]receive client auth failed message and go offline. {}", message.getMsgString());
|
||||
return null;
|
||||
}
|
||||
return ClusterMsg.Message.newBuilder()
|
||||
.setIdentity(message.getIdentity())
|
||||
.setDirection(ClusterMsg.Direction.RESPONSE)
|
||||
.setMsg(ByteString.copyFromUtf8(String.valueOf(CommonConstants.SUCCESS_CODE)))
|
||||
return ClusterMessage.builder()
|
||||
.identity(message.getIdentity())
|
||||
.direction(ClusterMessage.Direction.RESPONSE)
|
||||
.type(ClusterMessage.MessageType.GO_OFFLINE)
|
||||
.msg(String.valueOf(CommonConstants.SUCCESS_CODE).getBytes(StandardCharsets.UTF_8))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
+12
-16
@@ -6,7 +6,7 @@
|
||||
* (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
|
||||
* 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,
|
||||
@@ -17,13 +17,11 @@
|
||||
|
||||
package org.apache.hertzbeat.collector.dispatch.entrance.processor;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.collector.timer.TimerDispatch;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.dto.ServerInfo;
|
||||
import org.apache.hertzbeat.common.entity.message.ClusterMsg;
|
||||
import org.apache.hertzbeat.common.entity.message.ClusterMessage;
|
||||
import org.apache.hertzbeat.common.support.SpringContextHolder;
|
||||
import org.apache.hertzbeat.common.util.AesUtil;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
@@ -39,28 +37,26 @@ public class GoOnlineProcessor implements NettyRemotingProcessor {
|
||||
private TimerDispatch timerDispatch;
|
||||
|
||||
@Override
|
||||
public ClusterMsg.Message handle(ChannelHandlerContext ctx, ClusterMsg.Message message) {
|
||||
public ClusterMessage handle(ChannelHandlerContext ctx, ClusterMessage message) {
|
||||
if (this.timerDispatch == null) {
|
||||
this.timerDispatch = SpringContextHolder.getBean(TimerDispatch.class);
|
||||
}
|
||||
if (message.getMsg().isEmpty()) {
|
||||
String msgString = message.getMsgString();
|
||||
if (message.getMsg() == null || msgString == null || msgString.isEmpty()) {
|
||||
log.warn("The message that server response to collector is empty, please upgrade server");
|
||||
} else {
|
||||
ServerInfo serverInfo = JsonUtil.fromJson(message.getMsg().toStringUtf8(), ServerInfo.class);
|
||||
// Use the new JsonUtil.fromJson(byte[], Class) method
|
||||
ServerInfo serverInfo = JsonUtil.fromJson(message.getMsg(), ServerInfo.class);
|
||||
if (serverInfo == null || serverInfo.getAesSecret() == null) {
|
||||
log.warn("The message that server response to collector has not secret empty, please check");
|
||||
} else {
|
||||
AesUtil.setDefaultSecretKey(serverInfo.getAesSecret());
|
||||
}
|
||||
}
|
||||
if (ClusterMsg.Direction.REQUEST.equals(message.getDirection())) {
|
||||
timerDispatch.goOnline();
|
||||
}
|
||||
timerDispatch.goOnline();
|
||||
log.info("receive online message and handle success");
|
||||
return ClusterMsg.Message.newBuilder()
|
||||
.setIdentity(message.getIdentity())
|
||||
.setDirection(ClusterMsg.Direction.RESPONSE)
|
||||
.setMsg(ByteString.copyFromUtf8(String.valueOf(CommonConstants.SUCCESS_CODE)))
|
||||
.build();
|
||||
// Return null to stop the ping-pong loop.
|
||||
// The collector should not reply to the server's confirmation response.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -19,7 +19,7 @@ package org.apache.hertzbeat.collector.dispatch.entrance.processor;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.common.entity.message.ClusterMsg;
|
||||
import org.apache.hertzbeat.common.entity.message.ClusterMessage;
|
||||
import org.apache.hertzbeat.remoting.netty.NettyRemotingProcessor;
|
||||
|
||||
/**
|
||||
@@ -28,7 +28,7 @@ import org.apache.hertzbeat.remoting.netty.NettyRemotingProcessor;
|
||||
@Slf4j
|
||||
public class HeartbeatProcessor implements NettyRemotingProcessor {
|
||||
@Override
|
||||
public ClusterMsg.Message handle(ChannelHandlerContext ctx, ClusterMsg.Message message) {
|
||||
public ClusterMessage handle(ChannelHandlerContext ctx, ClusterMessage message) {
|
||||
log.info("collector receive manager server response heartbeat, time: {}. ", System.currentTimeMillis());
|
||||
return null;
|
||||
}
|
||||
|
||||
-22
@@ -25,8 +25,6 @@ import org.apache.hertzbeat.common.queue.CommonDataQueue;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* for collector instance
|
||||
* send collect response data by netty
|
||||
@@ -110,24 +108,4 @@ public class NettyDataQueue implements CommonDataQueue {
|
||||
public LogEntry pollLogEntryToStorage() throws InterruptedException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendLogEntryToAlertBatch(List<LogEntry> logEntries) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<LogEntry> pollLogEntryToAlertBatch(int maxBatchSize) throws InterruptedException {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendLogEntryToStorageBatch(List<LogEntry> logEntries) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<LogEntry> pollLogEntryToStorageBatch(int maxBatchSize) throws InterruptedException {
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
||||
-105
@@ -1,105 +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.dispatch.entrance.processor;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.protobuf.ByteString;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import org.apache.hertzbeat.collector.timer.TimerDispatch;
|
||||
import org.apache.hertzbeat.collector.timer.TimerDispatcher;
|
||||
import org.apache.hertzbeat.common.entity.job.Job;
|
||||
import org.apache.hertzbeat.common.entity.job.Metrics;
|
||||
import org.apache.hertzbeat.common.entity.message.ClusterMsg;
|
||||
import org.apache.hertzbeat.common.support.SpringContextHolder;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* Test for GoOnlineProcessor
|
||||
*/
|
||||
class GoOnlineProcessorTest {
|
||||
|
||||
private GoOnlineProcessor goOnlineProcessor;
|
||||
private TimerDispatcher timerDispatcher;
|
||||
|
||||
@Mock
|
||||
private ChannelHandlerContext channelHandlerContext;
|
||||
|
||||
private MockedStatic<SpringContextHolder> springContextHolderMockedStatic;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
goOnlineProcessor = new GoOnlineProcessor();
|
||||
timerDispatcher = new TimerDispatcher();
|
||||
springContextHolderMockedStatic = Mockito.mockStatic(SpringContextHolder.class);
|
||||
springContextHolderMockedStatic.when(() -> SpringContextHolder.getBean(TimerDispatch.class)).thenReturn(timerDispatcher);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() throws Exception {
|
||||
springContextHolderMockedStatic.close();
|
||||
timerDispatcher.destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
void verifyTaskMapPreservation() throws Exception {
|
||||
Job job = Job.builder()
|
||||
.app("test")
|
||||
.id(12345L)
|
||||
.metrics(Lists.newArrayList(Metrics.builder().interval(100L).build()))
|
||||
.configmap(Lists.newArrayList())
|
||||
.isCyclic(true)
|
||||
.build();
|
||||
timerDispatcher.addJob(job, null);
|
||||
|
||||
Field cyclicTaskMapField = TimerDispatcher.class.getDeclaredField("currentCyclicTaskMap");
|
||||
cyclicTaskMapField.setAccessible(true);
|
||||
Map<?, ?> currentCyclicTaskMap = (Map<?, ?>) cyclicTaskMapField.get(timerDispatcher);
|
||||
assertEquals(1, currentCyclicTaskMap.size(), "Task map should have 1 job initially");
|
||||
|
||||
ClusterMsg.Message responseMsg = ClusterMsg.Message.newBuilder()
|
||||
.setType(ClusterMsg.MessageType.GO_ONLINE)
|
||||
.setDirection(ClusterMsg.Direction.RESPONSE)
|
||||
.setMsg(ByteString.copyFromUtf8(JsonUtil.toJson(job)))
|
||||
.setIdentity("test-identity")
|
||||
.build();
|
||||
goOnlineProcessor.handle(channelHandlerContext, responseMsg);
|
||||
assertEquals(1, currentCyclicTaskMap.size(), "Task map should still have 1 job after receiving RESPONSE");
|
||||
|
||||
ClusterMsg.Message requestMsg = ClusterMsg.Message.newBuilder()
|
||||
.setType(ClusterMsg.MessageType.GO_ONLINE)
|
||||
.setDirection(ClusterMsg.Direction.REQUEST)
|
||||
.setMsg(ByteString.copyFromUtf8(JsonUtil.toJson(job)))
|
||||
.setIdentity("test-identity")
|
||||
.build();
|
||||
goOnlineProcessor.handle(channelHandlerContext, requestMsg);
|
||||
assertEquals(0, currentCyclicTaskMap.size(), "Task map should be empty after receiving REQUEST");
|
||||
}
|
||||
}
|
||||
+104
-61
@@ -6,7 +6,7 @@
|
||||
* (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
|
||||
* 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,
|
||||
@@ -17,7 +17,6 @@
|
||||
|
||||
package org.apache.hertzbeat.collector.collect.nebulagraph;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
@@ -25,7 +24,17 @@ import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hc.client5.http.config.RequestConfig;
|
||||
import org.apache.hc.client5.http.protocol.HttpClientContext;
|
||||
import org.apache.hc.core5.http.ClassicHttpRequest;
|
||||
import org.apache.hc.core5.http.HttpHeaders;
|
||||
import org.apache.hc.core5.http.io.HttpClientResponseHandler;
|
||||
import org.apache.hc.core5.http.io.entity.EntityUtils;
|
||||
import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
|
||||
import org.apache.hc.core5.http.protocol.HttpContext;
|
||||
import org.apache.hc.core5.util.Timeout;
|
||||
import org.apache.hertzbeat.collector.collect.AbstractCollect;
|
||||
import org.apache.hertzbeat.collector.collect.common.http.CommonHttpClient;
|
||||
import org.apache.hertzbeat.collector.constants.CollectorConstants;
|
||||
@@ -37,18 +46,9 @@ import org.apache.hertzbeat.common.entity.job.protocol.NebulaGraphProtocol;
|
||||
import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
import org.apache.hertzbeat.common.util.CommonUtil;
|
||||
import org.apache.hertzbeat.common.util.IpDomainUtil;
|
||||
import org.apache.http.HttpHeaders;
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpUriRequest;
|
||||
import org.apache.http.client.methods.RequestBuilder;
|
||||
import org.apache.http.client.protocol.HttpClientContext;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
|
||||
/**
|
||||
* NebulaGraph collect
|
||||
* NebulaGraph collect
|
||||
*/
|
||||
@Slf4j
|
||||
public class NebulaGraphCollectImpl extends AbstractCollect {
|
||||
@@ -66,6 +66,27 @@ public class NebulaGraphCollectImpl extends AbstractCollect {
|
||||
|
||||
private static final String STORAGE_API = "/rocksdb_stats";
|
||||
|
||||
/**
|
||||
* Inner class to wrap HTTP request with its configuration
|
||||
*/
|
||||
private static class HttpRequestWithConfig {
|
||||
private final ClassicHttpRequest request;
|
||||
private final RequestConfig requestConfig;
|
||||
|
||||
public HttpRequestWithConfig(ClassicHttpRequest request, RequestConfig requestConfig) {
|
||||
this.request = request;
|
||||
this.requestConfig = requestConfig;
|
||||
}
|
||||
|
||||
public ClassicHttpRequest getRequest() {
|
||||
return request;
|
||||
}
|
||||
|
||||
public RequestConfig getRequestConfig() {
|
||||
return requestConfig;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preCheck(Metrics metrics) throws IllegalArgumentException {
|
||||
if (metrics == null || metrics.getNebulaGraph() == null) {
|
||||
@@ -91,37 +112,50 @@ public class NebulaGraphCollectImpl extends AbstractCollect {
|
||||
return;
|
||||
}
|
||||
|
||||
String resp;
|
||||
long responseTime;
|
||||
HashMap<String, String> resultMap = new HashMap<>(64);
|
||||
HttpContext httpContext = createHttpContext(nebulaGraph.getHost(), nebulaGraph.getPort());
|
||||
HttpUriRequest request = createHttpRequest(nebulaGraph.getHost(), nebulaGraph.getPort(),
|
||||
// Create Request and Config
|
||||
HttpRequestWithConfig requestWithConfig = createHttpRequest(nebulaGraph.getHost(), nebulaGraph.getPort(),
|
||||
nebulaGraph.getUrl(), nebulaGraph.getTimeout());
|
||||
|
||||
// Create Context and set Config
|
||||
HttpContext httpContext = createHttpContext(requestWithConfig.getRequestConfig());
|
||||
ClassicHttpRequest request = requestWithConfig.getRequest();
|
||||
|
||||
// Send an HTTP request to obtain response data
|
||||
try (CloseableHttpResponse response = CommonHttpClient.getHttpClient().execute(request, httpContext)) {
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
if (statusCode != SUCCESS_CODE) {
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg("StatusCode " + statusCode);
|
||||
return;
|
||||
}
|
||||
resp = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
|
||||
responseTime = System.currentTimeMillis() - startTime;
|
||||
resultMap.put(CollectorConstants.RESPONSE_TIME, Long.toString(responseTime));
|
||||
// Parse the response differently depending on the API
|
||||
if (GRAPH_API.equals(nebulaGraph.getUrl())) {
|
||||
parseStatsResponse(resp, nebulaGraph.getTimePeriod(), resultMap);
|
||||
} else if (STORAGE_API.equals(nebulaGraph.getUrl())) {
|
||||
parseStorageResponse(resp, resultMap);
|
||||
}
|
||||
List<String> aliasFields = metrics.getAliasFields();
|
||||
CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
|
||||
for (String field : aliasFields) {
|
||||
String fieldValue = resultMap.get(field);
|
||||
valueRowBuilder.addColumn(Objects.requireNonNullElse(fieldValue, CommonConstants.NULL_VALUE));
|
||||
}
|
||||
builder.addValueRow(valueRowBuilder.build());
|
||||
} catch (IOException e) {
|
||||
try {
|
||||
// Use ResponseHandler to avoid manual resource management and CloseableHttpResponse deprecation issues
|
||||
HttpClientResponseHandler<Void> responseHandler = response -> {
|
||||
int statusCode = response.getCode();
|
||||
if (statusCode != SUCCESS_CODE) {
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg("StatusCode " + statusCode);
|
||||
return null;
|
||||
}
|
||||
|
||||
String resp = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
|
||||
long responseTime = System.currentTimeMillis() - startTime;
|
||||
HashMap<String, String> resultMap = new HashMap<>(64);
|
||||
resultMap.put(CollectorConstants.RESPONSE_TIME, Long.toString(responseTime));
|
||||
|
||||
// Parse the response differently depending on the API
|
||||
if (GRAPH_API.equals(nebulaGraph.getUrl())) {
|
||||
parseStatsResponse(resp, nebulaGraph.getTimePeriod(), resultMap);
|
||||
} else if (STORAGE_API.equals(nebulaGraph.getUrl())) {
|
||||
parseStorageResponse(resp, resultMap);
|
||||
}
|
||||
|
||||
List<String> aliasFields = metrics.getAliasFields();
|
||||
CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
|
||||
for (String field : aliasFields) {
|
||||
String fieldValue = resultMap.get(field);
|
||||
valueRowBuilder.addColumn(Objects.requireNonNullElse(fieldValue, CommonConstants.NULL_VALUE));
|
||||
}
|
||||
builder.addValueRow(valueRowBuilder.build());
|
||||
return null;
|
||||
};
|
||||
|
||||
CommonHttpClient.getHttpClient().execute(request, httpContext, responseHandler);
|
||||
|
||||
} catch (Exception e) {
|
||||
String errorMsg = CommonUtil.getMessageFromThrowable(e);
|
||||
log.info(errorMsg);
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
@@ -134,43 +168,47 @@ public class NebulaGraphCollectImpl extends AbstractCollect {
|
||||
return DispatchConstants.PROTOCOL_NEBULAGRAPH;
|
||||
}
|
||||
|
||||
private HttpContext createHttpContext(String host, String port) {
|
||||
HttpHost httpHost = new HttpHost(host, Integer.parseInt(port));
|
||||
HttpClientContext httpClientContext = new HttpClientContext();
|
||||
httpClientContext.setTargetHost(httpHost);
|
||||
return httpClientContext;
|
||||
private HttpContext createHttpContext(RequestConfig requestConfig) {
|
||||
HttpClientContext context = HttpClientContext.create();
|
||||
if (requestConfig != null) {
|
||||
context.setRequestConfig(requestConfig);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
private HttpUriRequest createHttpRequest(String host, String port, String url, String timeoutStr) {
|
||||
RequestBuilder requestBuilder = RequestBuilder.get();
|
||||
// uri
|
||||
@SuppressWarnings("deprecation")
|
||||
private HttpRequestWithConfig createHttpRequest(String host, String port, String url, String timeoutStr) {
|
||||
// HttpClient 5 uses ClassicRequestBuilder
|
||||
ClassicRequestBuilder requestBuilder = ClassicRequestBuilder.get();
|
||||
|
||||
// uri construction
|
||||
String uri = CollectUtil.replaceUriSpecialChar(url);
|
||||
if (IpDomainUtil.isHasSchema(host)) {
|
||||
requestBuilder.setUri(host + ":" + port + uri);
|
||||
} else {
|
||||
String ipAddressType = IpDomainUtil.checkIpAddressType(host);
|
||||
String baseUri = CollectorConstants.IPV6.equals(ipAddressType)
|
||||
? String.format("[%s]:%s", host, port + uri)
|
||||
: String.format("%s:%s", host, port + uri);
|
||||
? String.format("[%s]:%s%s", host, port, uri)
|
||||
: String.format("%s:%s%s", host, port, uri);
|
||||
|
||||
requestBuilder.setUri(CollectorConstants.HTTP_HEADER + baseUri);
|
||||
}
|
||||
|
||||
requestBuilder.addHeader(HttpHeaders.CONNECTION, "keep-alive");
|
||||
requestBuilder.addHeader(HttpHeaders.USER_AGENT, "Mozilla/5.0 (Windows NT 6.1; WOW64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.76 Safari/537.36");
|
||||
|
||||
requestBuilder.addHeader(HttpHeaders.ACCEPT, "text/plain");
|
||||
|
||||
RequestConfig requestConfig = null;
|
||||
int timeout = Integer.parseInt(timeoutStr);
|
||||
if (timeout > 0) {
|
||||
RequestConfig requestConfig = RequestConfig.custom()
|
||||
.setConnectTimeout(timeout)
|
||||
.setSocketTimeout(timeout)
|
||||
// Use setConnectTimeout despite deprecation to allow per-request connection timeout override on a shared client
|
||||
requestConfig = RequestConfig.custom()
|
||||
.setConnectTimeout(Timeout.ofMilliseconds(timeout))
|
||||
.setResponseTimeout(Timeout.ofMilliseconds(timeout))
|
||||
.setRedirectsEnabled(true)
|
||||
.build();
|
||||
requestBuilder.setConfig(requestConfig);
|
||||
}
|
||||
return requestBuilder.build();
|
||||
return new HttpRequestWithConfig(requestBuilder.build(), requestConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -178,6 +216,7 @@ public class NebulaGraphCollectImpl extends AbstractCollect {
|
||||
*
|
||||
* @param responseBody response body
|
||||
* @param timePeriod time period
|
||||
* @param resultMap result map
|
||||
*/
|
||||
private void parseStatsResponse(String responseBody, String timePeriod, HashMap<String, String> resultMap) {
|
||||
// Set up regular expression matching
|
||||
@@ -188,22 +227,26 @@ public class NebulaGraphCollectImpl extends AbstractCollect {
|
||||
Matcher matcher = pattern.matcher(str);
|
||||
if (matcher.find()) {
|
||||
String[] split = str.split(timeRegex);
|
||||
resultMap.put(split[0], split[1]);
|
||||
if (split.length > 1) {
|
||||
resultMap.put(split[0], split[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse the Storage response and filter by time period
|
||||
*
|
||||
* @param responseBody response body
|
||||
* @param resultMap result map
|
||||
*/
|
||||
private void parseStorageResponse(String responseBody, HashMap<String, String> resultMap) {
|
||||
String[] strArray = responseBody.split(STR_SPLIT);
|
||||
for (String str : strArray) {
|
||||
String[] split = str.split(STORAGE_SPLIT_KEY_VALUE);
|
||||
resultMap.put(split[0], split[1]);
|
||||
if (split.length > 1) {
|
||||
resultMap.put(split[0], split[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@
|
||||
<name>${project.artifactId}</name>
|
||||
<packaging>pom</packaging>
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
<java.version>21</java.version>
|
||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||
<maven.compiler.target>${java.version}</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
+18
-19
@@ -46,20 +46,9 @@
|
||||
<artifactId>spring-boot-autoconfigure</artifactId>
|
||||
</dependency>
|
||||
<!-- jpa -->
|
||||
<dependency>
|
||||
<groupId>org.eclipse.persistence</groupId>
|
||||
<artifactId>org.eclipse.persistence.jpa</artifactId>
|
||||
<version>${eclipse-jpa.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.hibernate.orm</groupId>
|
||||
<artifactId>hibernate-core</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!-- validation -->
|
||||
<dependency>
|
||||
@@ -76,6 +65,10 @@
|
||||
<groupId>com.fasterxml.jackson.dataformat</groupId>
|
||||
<artifactId>jackson-dataformat-xml</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-jackson2</artifactId>
|
||||
</dependency>
|
||||
<!-- swagger -->
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
@@ -95,11 +88,6 @@
|
||||
<groupId>com.google.protobuf</groupId>
|
||||
<artifactId>protobuf-java-util</artifactId>
|
||||
</dependency>
|
||||
<!-- Use apache.http.conn.util instead of sun.net.util -->
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
</dependency>
|
||||
<!-- caffeine-->
|
||||
<dependency>
|
||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||
@@ -171,7 +159,7 @@
|
||||
<groupId>org.apache.arrow</groupId>
|
||||
<artifactId>arrow-memory-netty</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.xerial.snappy</groupId>
|
||||
<artifactId>snappy-java</artifactId>
|
||||
@@ -184,8 +172,19 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.jsqlparser</groupId>
|
||||
<artifactId>jsqlparser</artifactId>
|
||||
<groupId>org.apache.fory</groupId>
|
||||
<artifactId>fory-core</artifactId>
|
||||
<version>${fory.version}</version>
|
||||
</dependency>
|
||||
<!-- row/arrow format support -->
|
||||
<dependency>
|
||||
<groupId>org.apache.fory</groupId>
|
||||
<artifactId>fory-format</artifactId>
|
||||
<version>${fory.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents.client5</groupId>
|
||||
<artifactId>httpclient5</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
-68
@@ -1,68 +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.common.config;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.eclipse.persistence.config.SessionCustomizer;
|
||||
import org.eclipse.persistence.descriptors.ClassDescriptor;
|
||||
import org.eclipse.persistence.mappings.DatabaseMapping;
|
||||
import org.eclipse.persistence.sessions.Session;
|
||||
|
||||
/**
|
||||
* change column name that from upper-case to lower-case-with-underscore
|
||||
*/
|
||||
@Slf4j
|
||||
public class EclipseLinkCustomizer implements SessionCustomizer {
|
||||
|
||||
@Override
|
||||
public void customize(Session session) throws Exception {
|
||||
for (ClassDescriptor descriptor : session.getDescriptors().values()) {
|
||||
for (DatabaseMapping mapping : descriptor.getMappings()) {
|
||||
if (mapping.isDirectToFieldMapping()) {
|
||||
// update the column name to lower case with underscore
|
||||
if (!mapping.getField().getName().equalsIgnoreCase(mapping.getAttributeName())) {
|
||||
// already custom define the column name, ignore
|
||||
continue;
|
||||
}
|
||||
// todo here i try to change column name that from upper-case to lower-case-with-underscore
|
||||
// but failed, when update name, something relate exception happen more
|
||||
mapping.getField().setName(convertCamelCase(mapping.getAttributeName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private String convertCamelCase(String camelCase) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
if (camelCase != null && !camelCase.isEmpty()) {
|
||||
result.append(Character.toLowerCase(camelCase.charAt(0)));
|
||||
for (int i = 1; i < camelCase.length(); i++) {
|
||||
char currentChar = camelCase.charAt(i);
|
||||
if (Character.isUpperCase(currentChar)) {
|
||||
result.append('_').append(Character.toLowerCase(currentChar));
|
||||
} else {
|
||||
result.append(currentChar);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
}
|
||||
-61
@@ -1,61 +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.common.config;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import javax.sql.DataSource;
|
||||
import org.eclipse.persistence.config.PersistenceUnitProperties;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration;
|
||||
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.orm.jpa.vendor.AbstractJpaVendorAdapter;
|
||||
import org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter;
|
||||
import org.springframework.transaction.jta.JtaTransactionManager;
|
||||
|
||||
/**
|
||||
* jpa eclipselink impl config
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnProperty(prefix = "spring.datasource", name = "url")
|
||||
public class EclipseLinkJpaConfiguration extends JpaBaseConfiguration {
|
||||
|
||||
protected EclipseLinkJpaConfiguration(DataSource dataSource, JpaProperties properties,
|
||||
ObjectProvider<JtaTransactionManager> jtaTransactionManager) {
|
||||
super(dataSource, properties, jtaTransactionManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractJpaVendorAdapter createJpaVendorAdapter() {
|
||||
return new EclipseLinkJpaVendorAdapter();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Map<String, Object> getVendorProperties() {
|
||||
HashMap<String, Object> map = new HashMap<>(8);
|
||||
map.put(PersistenceUnitProperties.DDL_GENERATION, "create-or-extend-tables");
|
||||
map.put(PersistenceUnitProperties.SESSION_CUSTOMIZER, "org.apache.hertzbeat.common.config.EclipseLinkCustomizer");
|
||||
map.put(PersistenceUnitProperties.ALLOW_NATIVE_SQL_QUERIES, "true");
|
||||
map.put(PersistenceUnitProperties.WEAVING, "true");
|
||||
return map;
|
||||
}
|
||||
}
|
||||
+5
@@ -97,6 +97,11 @@ public interface CommonConstants {
|
||||
*/
|
||||
String LABEL_ALERT_NAME = "alertname";
|
||||
|
||||
/**
|
||||
* label key: instance host
|
||||
*/
|
||||
String LABEL_INSTANCE_HOST = "instancehost";
|
||||
|
||||
/**
|
||||
* label key: instance name
|
||||
*/
|
||||
|
||||
-43
@@ -1,43 +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.common.constants;
|
||||
|
||||
/**
|
||||
* HertzBeat Reserved Keywords Enum
|
||||
*/
|
||||
public enum HertzBeatKeywordsEnum {
|
||||
|
||||
INSTANCE("instance", "metric_instance");
|
||||
|
||||
private final String keyword;
|
||||
|
||||
private final String alias;
|
||||
|
||||
HertzBeatKeywordsEnum(String keyword, String alias) {
|
||||
this.keyword = keyword;
|
||||
this.alias = alias;
|
||||
}
|
||||
|
||||
public String getKeyword() {
|
||||
return keyword;
|
||||
}
|
||||
|
||||
public String getAlias() {
|
||||
return alias;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -38,5 +38,5 @@ public interface MetricDataConstants {
|
||||
String CODE = "code";
|
||||
String MSG = "msg";
|
||||
String INSTANCE_NAME = "instancename";
|
||||
String INSTANCE = "instance";
|
||||
String INSTANCE_HOST = "instancehost";
|
||||
}
|
||||
|
||||
@@ -1,50 +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.common.constants;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* Theme Enum
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
@ToString
|
||||
public enum ThemeEnum {
|
||||
|
||||
COMPACT("compact"), DEFAULT("default"), LIGHT("light"), DARK("dark");
|
||||
|
||||
private final String value;
|
||||
|
||||
/**
|
||||
* Convert theme to standard theme
|
||||
*
|
||||
* @param theme the input theme
|
||||
* @return the converted theme
|
||||
*/
|
||||
public static String convert(String theme) {
|
||||
if ((theme == null || theme.isEmpty())
|
||||
|| ThemeEnum.COMPACT.getValue().equals(theme)
|
||||
|| ThemeEnum.DEFAULT.getValue().equals(theme)) {
|
||||
return ThemeEnum.LIGHT.getValue();
|
||||
}
|
||||
return theme;
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -6,7 +6,7 @@
|
||||
* (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
|
||||
* 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,
|
||||
@@ -55,7 +55,7 @@ public class ChatConversation {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
|
||||
@Schema(title = "conversation title")
|
||||
private String title;
|
||||
|
||||
@@ -79,6 +79,6 @@ public class ChatConversation {
|
||||
* List of messages in this conversation (one-to-many relationship)
|
||||
*/
|
||||
@OneToMany
|
||||
@JoinColumn(name = "conversation_id")
|
||||
@JoinColumn(name = "conversation_id", insertable = false, updatable = false)
|
||||
private List<ChatMessage> messages;
|
||||
}
|
||||
}
|
||||
+7
-5
@@ -6,7 +6,7 @@
|
||||
* (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
|
||||
* 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,
|
||||
@@ -19,6 +19,7 @@ package org.apache.hertzbeat.common.entity.ai;
|
||||
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_ONLY;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EntityListeners;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
@@ -48,7 +49,7 @@ import lombok.NoArgsConstructor;
|
||||
@Entity
|
||||
@EntityListeners(AuditingEntityListener.class)
|
||||
@Table(name = "hzb_ai_message", indexes = {
|
||||
@Index(name = "idx_message_conversation_id", columnList = "conversation_id")
|
||||
@Index(name = "idx_message_conversation_id", columnList = "conversation_id")
|
||||
})
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@@ -57,8 +58,9 @@ public class ChatMessage {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
|
||||
@Schema(title = "conversation id")
|
||||
@Column(name = "conversation_id")
|
||||
private Long conversationId;
|
||||
|
||||
@Schema(title = "message content")
|
||||
@@ -84,5 +86,5 @@ public class ChatMessage {
|
||||
@Schema(title = "Record modify time", example = "1612198444000", accessMode = READ_ONLY)
|
||||
@LastModifiedDate
|
||||
private LocalDateTime gmtUpdate;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+1
@@ -101,6 +101,7 @@ public class AlertDefine {
|
||||
private String datasource;
|
||||
|
||||
@Schema(title = "Is Enabled", example = "true")
|
||||
@Builder.Default
|
||||
private boolean enable = true;
|
||||
|
||||
@Schema(title = "The creator of this record", example = "tom", accessMode = READ_ONLY)
|
||||
|
||||
+2
-2
@@ -48,8 +48,8 @@ import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "hzb_alert_define_monitor_bind", indexes = {
|
||||
@Index(name = "idx_alert_define_id", columnList = "alert_define_id"),
|
||||
@Index(name = "idx_monitor_id", columnList = "monitor_id")
|
||||
@Index(name = "index_alert_define_monitor", columnList = "alertDefineId"),
|
||||
@Index(name = "index_alert_define_monitor", columnList = "monitor_id")
|
||||
})
|
||||
@Data
|
||||
@Builder
|
||||
|
||||
+2
@@ -69,9 +69,11 @@ public class AlertSilence {
|
||||
private String name;
|
||||
|
||||
@Schema(title = "Whether to enable this policy", example = "true")
|
||||
@Builder.Default
|
||||
private boolean enable = true;
|
||||
|
||||
@Schema(title = "Whether to match all", example = "true")
|
||||
@Builder.Default
|
||||
private boolean matchAll = true;
|
||||
|
||||
@Schema(title = "Silence type 0: once, 1:cyc", example = "1")
|
||||
|
||||
+2
@@ -103,11 +103,13 @@ public class NoticeRule {
|
||||
@Schema(title = "Whether to enable this policy",
|
||||
description = "Whether to enable this policy",
|
||||
example = "true", accessMode = READ_WRITE)
|
||||
@Builder.Default
|
||||
private boolean enable = true;
|
||||
|
||||
@Schema(title = "Whether to forward all",
|
||||
description = "Whether to forward all",
|
||||
example = "false", accessMode = READ_WRITE)
|
||||
@Builder.Default
|
||||
private boolean filterAll = true;
|
||||
|
||||
@Schema(title = "Labels", example = "{\"alertname\": \"HighCPUUsage\", \"priority\": \"critical\", \"instance\": \"343483943\"}")
|
||||
|
||||
+1
@@ -85,6 +85,7 @@ public class NoticeTemplate {
|
||||
description = "Is it a preset template: true- preset template false- custom template.",
|
||||
accessMode = READ_WRITE)
|
||||
@Column(columnDefinition = "boolean default false")
|
||||
@Builder.Default
|
||||
private boolean preset = false;
|
||||
|
||||
@Schema(title = "Template content",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user