This is an automated email from the ASF dual-hosted git repository.
tanxinyu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iotdb.git
The following commit(s) were added to refs/heads/master by this push:
new a6401e4 [IOTDB-1319] Trigger module: alert manager sink (#3057)
a6401e4 is described below
commit a6401e4c4c308375dab817a14634f7d31b3b4755
Author: mzp0514 <[email protected]>
AuthorDate: Wed Apr 28 20:06:17 2021 +0800
[IOTDB-1319] Trigger module: alert manager sink (#3057)
Co-authored-by: SteveYurongSu <[email protected]>
---
docs/UserGuide/Advanced-Features/Alerting.md | 385 +++++++++++++++++++++
docs/UserGuide/Advanced-Features/Triggers.md | 108 ++++++
docs/zh/UserGuide/Advanced-Features/Alerting.md | 385 +++++++++++++++++++++
docs/zh/UserGuide/Advanced-Features/Triggers.md | 107 ++++++
.../org/apache/iotdb/trigger/AlertingExample.java | 107 ++++++
server/pom.xml | 6 +
.../alertmanager/AlertManagerConfiguration.java | 35 ++
.../db/sink/alertmanager/AlertManagerEvent.java | 123 +++++++
.../db/sink/alertmanager/AlertManagerHandler.java | 88 +++++
.../org/apache/iotdb/db/sink/AlertManagerTest.java | 336 ++++++++++++++++++
10 files changed, 1680 insertions(+)
diff --git a/docs/UserGuide/Advanced-Features/Alerting.md
b/docs/UserGuide/Advanced-Features/Alerting.md
new file mode 100644
index 0000000..1a4fd3a
--- /dev/null
+++ b/docs/UserGuide/Advanced-Features/Alerting.md
@@ -0,0 +1,385 @@
+<!--
+
+ 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.
+
+-->
+
+# Alerting
+
+## Overview
+The alerting of IoTDB is expected to support two modes:
+
+* Writing triggered: the user writes data to the original time series, and
every time a piece of data is inserted, the judgment logic of `trigger` will be
triggered.
+If the alerting requirements are met, an alert is sent to the data sink,
+The data sink then forwards the alert to the external terminal.
+ * This mode is suitable for scenarios that need to monitor every piece of
data in real time.
+ * Since the operation in the trigger will affect the data writing
performance, it is suitable for scenarios that are not sensitive to the
original data writing performance.
+
+* Continuous query: the user writes data to the original time series,
+`ContinousQuery` periodically queries the original time series, and writes the
query results into the new time series,
+Each write triggers the judgment logic of `trigger`,
+If the alerting requirements are met, an alert is sent to the data sink,
+The data sink then forwards the alert to the external terminal.
+ * This mode is suitable for scenarios where data needs to be regularly
queried within a certain period of time.
+ * It is Suitable for scenarios where the original data needs to be
down-sampled and persisted.
+ * Since the timing query hardly affects the writing of the original time
series, it is suitable for scenarios that are sensitive to the performance of
the original data writing performance.
+
+With the introduction of the `trigger` module and the `sink` module into IoTDB,
+at present, users can use these two modules with `AlertManager` to realize the
writing triggered alerting mode.
+
+
+
+## Deploying AlertManager
+
+### Installation
+#### Precompiled binaries
+The pre-compiled binary file can be downloaded
[here](https://prometheus.io/download/).
+
+Running command:
+````shell
+./alertmanager --config.file=<your_file>
+````
+
+#### Docker image
+Available at [Quay.io](https://hub.docker.com/r/prom/alertmanager/)
+or [Docker Hub](https://quay.io/repository/prometheus/alertmanager).
+
+Running command:
+````shell
+docker run --name alertmanager -d -p 127.0.0.1:9093:9093
quay.io/prometheus/alertmanager
+````
+
+### Configuration
+
+The following is an example, which can cover most of the configuration rules.
For detailed configuration rules, see
+[here](https://prometheus.io/docs/alerting/latest/configuration/).
+
+Example:
+``` yaml
+# alertmanager.yml
+
+global:
+ # The smarthost and SMTP sender used for mail notifications.
+ smtp_smarthost: 'localhost:25'
+ smtp_from: '[email protected]'
+
+# The root route on which each incoming alert enters.
+route:
+ # The root route must not have any matchers as it is the entry point for
+ # all alerts. It needs to have a receiver configured so alerts that do not
+ # match any of the sub-routes are sent to someone.
+ receiver: 'team-X-mails'
+
+ # The labels by which incoming alerts are grouped together. For example,
+ # multiple alerts coming in for cluster=A and alertname=LatencyHigh would
+ # be batched into a single group.
+ #
+ # To aggregate by all possible labels use '...' as the sole label name.
+ # This effectively disables aggregation entirely, passing through all
+ # alerts as-is. This is unlikely to be what you want, unless you have
+ # a very low alert volume or your upstream notification system performs
+ # its own grouping. Example: group_by: [...]
+ group_by: ['alertname', 'cluster']
+
+ # When a new group of alerts is created by an incoming alert, wait at
+ # least 'group_wait' to send the initial notification.
+ # This way ensures that you get multiple alerts for the same group that start
+ # firing shortly after another are batched together on the first
+ # notification.
+ group_wait: 30s
+
+ # When the first notification was sent, wait 'group_interval' to send a batch
+ # of new alerts that started firing for that group.
+ group_interval: 5m
+
+ # If an alert has successfully been sent, wait 'repeat_interval' to
+ # resend them.
+ repeat_interval: 3h
+
+ # All the above attributes are inherited by all child routes and can
+ # overwritten on each.
+
+ # The child route trees.
+ routes:
+ # This routes performs a regular expression match on alert labels to
+ # catch alerts that are related to a list of services.
+ - match_re:
+ service: ^(foo1|foo2|baz)$
+ receiver: team-X-mails
+
+ # The service has a sub-route for critical alerts, any alerts
+ # that do not match, i.e. severity != critical, fall-back to the
+ # parent node and are sent to 'team-X-mails'
+ routes:
+ - match:
+ severity: critical
+ receiver: team-X-pager
+
+ - match:
+ service: files
+ receiver: team-Y-mails
+
+ routes:
+ - match:
+ severity: critical
+ receiver: team-Y-pager
+
+ # This route handles all alerts coming from a database service. If there's
+ # no team to handle it, it defaults to the DB team.
+ - match:
+ service: database
+
+ receiver: team-DB-pager
+ # Also group alerts by affected database.
+ group_by: [alertname, cluster, database]
+
+ routes:
+ - match:
+ owner: team-X
+ receiver: team-X-pager
+
+ - match:
+ owner: team-Y
+ receiver: team-Y-pager
+
+
+# Inhibition rules allow to mute a set of alerts given that another alert is
+# firing.
+# We use this to mute any warning-level notifications if the same alert is
+# already critical.
+inhibit_rules:
+- source_match:
+ severity: 'critical'
+ target_match:
+ severity: 'warning'
+ # Apply inhibition if the alertname is the same.
+ # CAUTION:
+ # If all label names listed in `equal` are missing
+ # from both the source and target alerts,
+ # the inhibition rule will apply!
+ equal: ['alertname']
+
+
+receivers:
+- name: 'team-X-mails'
+ email_configs:
+ - to: '[email protected], [email protected]'
+
+- name: 'team-X-pager'
+ email_configs:
+ - to: '[email protected]'
+ pagerduty_configs:
+ - routing_key: <team-X-key>
+
+- name: 'team-Y-mails'
+ email_configs:
+ - to: '[email protected]'
+
+- name: 'team-Y-pager'
+ pagerduty_configs:
+ - routing_key: <team-Y-key>
+
+- name: 'team-DB-pager'
+ pagerduty_configs:
+ - routing_key: <team-DB-key>
+```
+
+In the following example, we used the following configuration:
+````yaml
+# alertmanager.yml
+
+global:
+ smtp_smarthost: ''
+ smtp_from: ''
+ smtp_auth_username: ''
+ smtp_auth_password: ''
+ smtp_require_tls: false
+
+route:
+ group_by: ['alertname']
+ group_wait: 1m
+ group_interval: 10m
+ repeat_interval: 10h
+ receiver: 'email'
+
+receivers:
+ - name: 'email'
+ email_configs:
+ - to: ''
+
+inhibit_rules:
+ - source_match:
+ severity: 'critical'
+ target_match:
+ severity: 'warning'
+ equal: ['alertname']
+````
+
+
+### API
+The `AlertManager` API is divided into two versions, `v1` and `v2`. The
current `AlertManager` API version is `v2`
+(For configuration see
+[api/v2/openapi.yaml](https://github.com/prometheus/alertmanager/blob/master/api/v2/openapi.yaml)).
+
+By default, the prefix is `/api/v1` or `/api/v2` and the endpoint for sending
alerts is `/api/v1/alerts` or `/api/v2/alerts`.
+If the user specifies `--web.route-prefix`,
+for example `--web.route-prefix=/alertmanager/`,
+then the prefix will become `/alertmanager/api/v1` or `/alertmanager/api/v2`,
+and the endpoint that sends the alert becomes `/alertmanager/api/v1/alerts`
+or `/alertmanager/api/v2/alerts`.
+
+## Creating trigger
+
+### Writing the trigger class
+
+The user defines a trigger by creating a Java class and writing the logic in
the hook.
+Please refer to [Triggers](Triggers.md) for the specific configuration process
and the usage method of `AlertManagerSink` related tools provided by the Sink
module.
+
+The following example creates the `org.apache.iotdb.trigger.AlertingExample`
class,
+Its alertManagerHandler member variables can send alerts to the AlertManager
instance
+at the address of `http://127.0.0.1:9093/`.
+
+When `value> 100.0`, send an alert of `critical` severity;
+when `50.0 <value <= 100.0`, send an alert of `warning` severity
+.
+````java
+package org.apache.iotdb.trigger;
+
+/*
+package importing is omitted here
+*/
+
+public class AlertingExample implements Trigger {
+
+ private final AlertManagerHandler alertManagerHandler = new
AlertManagerHandler();
+
+ private final AlertManagerConfiguration alertManagerConfiguration =
+ new AlertManagerConfiguration("http://127.0.0.1:9093/api/v2/alerts");
+
+ private String alertname;
+
+ private final HashMap<String, String> labels = new HashMap<>();
+
+ private final HashMap<String, String> annotations = new HashMap<>();
+
+ @Override
+ public void onCreate(TriggerAttributes attributes) throws Exception {
+ alertManagerHandler.open(alertManagerConfiguration);
+
+ alertname = "alert_test";
+
+ labels.put("series", "root.ln.wf01.wt01.temperature");
+ labels.put("value", "");
+ labels.put("severity", "");
+
+ annotations.put("summary", "high temperature");
+ annotations.put("description", "{{.alertname}}: {{.series}} is
{{.value}}");
+ }
+
+ @Override
+ public void onDrop() throws IOException {
+ alertManagerHandler.close();
+ }
+
+ @Override
+ public void onStart() {
+ alertManagerHandler.open(alertManagerConfiguration);
+ }
+
+ @Override
+ public void onStop() throws Exception {
+ alertManagerHandler.close();
+ }
+
+ @Override
+ public Double fire(long timestamp, Double value) throws Exception {
+ if (value > 100.0) {
+ labels.put("value", String.valueOf(value));
+ labels.put("severity", "critical");
+ AlertManagerEvent alertManagerEvent = new AlertManagerEvent(alertname,
labels, annotations);
+ alertManagerHandler.onEvent(alertManagerEvent);
+ } else if (value > 50.0) {
+ labels.put("value", String.valueOf(value));
+ labels.put("severity", "warning");
+ AlertManagerEvent alertManagerEvent = new AlertManagerEvent(alertname,
labels, annotations);
+ alertManagerHandler.onEvent(alertManagerEvent);
+ }
+
+ return value;
+ }
+
+ @Override
+ public double[] fire(long[] timestamps, double[] values) throws Exception {
+ for (double value : values) {
+ if (value > 100.0) {
+ labels.put("value", String.valueOf(value));
+ labels.put("severity", "critical");
+ AlertManagerEvent alertManagerEvent = new AlertManagerEvent(alertname,
labels, annotations);
+ alertManagerHandler.onEvent(alertManagerEvent);
+ } else if (value > 50.0) {
+ labels.put("value", String.valueOf(value));
+ labels.put("severity", "warning");
+ AlertManagerEvent alertManagerEvent = new AlertManagerEvent(alertname,
labels, annotations);
+ alertManagerHandler.onEvent(alertManagerEvent);
+ }
+ }
+ return values;
+ }
+}
+
+````
+
+### Creating trigger
+
+The following SQL statement registered the trigger
+named `root-ln-wf01-wt01-alert`
+on the `root.ln.wf01.wt01.temperature` time series,
+whose operation logic is defined
+by `org.apache.iotdb.trigger.AlertingExample` java class.
+
+``` sql
+ CREATE TRIGGER root-ln-wf01-wt01-alert
+ AFTER INSERT
+ ON root.ln.wf01.wt01.temperature
+ AS "org.apache.iotdb.trigger.AlertingExample"
+```
+
+
+## Writing data
+
+When we finish the deployment and startup of AlertManager as well as the
creation of Trigger,
+we can test the alerting
+by writing data to the time series.
+
+``` sql
+INSERT INTO root.ln.wf01.wt01(timestamp, temperature) VALUES (1, 0);
+INSERT INTO root.ln.wf01.wt01(timestamp, temperature) VALUES (2, 30);
+INSERT INTO root.ln.wf01.wt01(timestamp, temperature) VALUES (3, 60);
+INSERT INTO root.ln.wf01.wt01(timestamp, temperature) VALUES (4, 90);
+INSERT INTO root.ln.wf01.wt01(timestamp, temperature) VALUES (5, 120);
+```
+
+After executing the above writing statements,
+we can receive an alerting email. Because our `AlertManager` configuration
above
+makes alerts of `critical` severity inhibit those of `warning` severity,
+the alerting email we receive only contains the alert triggered
+by the writing of `(5, 120)`.
+
+<img width="669" alt="alerting"
src="https://user-images.githubusercontent.com/34649843/115957896-a9791080-a537-11eb-9962-541412bdcee6.png">
+
+
diff --git a/docs/UserGuide/Advanced-Features/Triggers.md
b/docs/UserGuide/Advanced-Features/Triggers.md
index eb72538..4352be1 100644
--- a/docs/UserGuide/Advanced-Features/Triggers.md
+++ b/docs/UserGuide/Advanced-Features/Triggers.md
@@ -568,6 +568,114 @@ for (int i = 0; i < 100; ++i) {
+#### AlertManagerSink
+
+In a trigger, you can use `AlertManagerSink` to send messages to AlertManager。
+
+You need to specify the endpoint to send alerts of your AlertManager when
constructing
+`AlertManagerConfiguration`
+
+```java
+AlertManagerConfiguration(String endpoint);
+```
+
+`AlertManagerEvent` offers three types of constructors:
+```java
+AlertManagerEvent(String alertname);
+AlertManagerEvent(String alertname, Map<String, String> extraLabels);
+AlertManagerEvent(String alertname, Map<String, String> extraLabels,
Map<String, String> annotations);
+```
+
+* `alertname` is a required parameter to identify an `alert`. The `alertname`
field can be used for grouping and deduplication when the `AlertManager` sends
an alert.
+* `extraLabels` is optional. In the backend, it is combined with `alertname`
to form `labels` to identify an `alert`, which can be used for grouping and
deduplication when `AlertManager` sends alarms.
+* `annotations` is optional, and its value can use Go style template
`{{.<label_key>}}`. `{{.<label_key>}}` will be replaced with
`labels[<label_key>]` when the message is finally generated.
+* `labels` and `annotations` will be parsed into json string and sent to
`AlertManager`:
+```json
+{
+ "labels": {
+ "alertname": "<requiredAlertName>",
+ "<labelname>": "<labelvalue>",
+ ...
+ },
+ "annotations": {
+ "<labelname>": "<labelvalue>",
+ ...
+ }
+}
+```
+
+Call the `onEvent(AlertManagerEvent event)` method of `AlertManagerHandler` to
send an alert.
+
+
+
+**Example 1:**
+
+Only pass `alertname`.
+
+```java
+AlertManagerHandler alertManagerHandler = new AlertManagerHandler();
+
+alertManagerHandler.open(new
AlertManagerConfiguration("http://127.0.0.1:9093/api/v1/alerts"));
+
+final String alertName = "test0";
+
+AlertManagerEvent alertManagerEvent = new AlertManagerEvent(alertName);
+
+alertManagerHandler.onEvent(alertManagerEvent);
+```
+
+
+
+**Example 2:**
+
+Pass `alertname` and `extraLabels`.
+
+```java
+AlertManagerHandler alertManagerHandler = new AlertManagerHandler();
+
+alertManagerHandler.open(new
AlertManagerConfiguration("http://127.0.0.1:9093/api/v1/alerts"));
+
+final String alertName = "test1";
+
+final HashMap<String, String> extraLabels = new HashMap<>();
+extraLabels.put("severity", "critical");
+extraLabels.put("series", "root.ln.wt01.wf01.temperature");
+extraLabels.put("value", String.valueOf(100.0));
+
+AlertManagerEvent alertManagerEvent = new AlertManagerEvent(alertName,
extraLabels);
+
+alertManagerHandler.onEvent(alertManagerEvent);
+```
+
+
+
+**Example 3:**
+
+Pass `alertname`, `extraLabels` 和 `annotations`.
+
+The final value of the `description` field will be parsed as `test2:
root.ln.wt01.wf01.temperature is 100.0`.
+
+```java
+AlertManagerHandler alertManagerHandler = new AlertManagerHandler();
+
+alertManagerHandler.open(new
AlertManagerConfiguration("http://127.0.0.1:9093/api/v1/alerts"));
+
+final String alertName = "test2";
+
+final HashMap<String, String> extraLabels = new HashMap<>();
+extraLabels.put("severity", "critical");
+extraLabels.put("series", "root.ln.wt01.wf01.temperature");
+extraLabels.put("value", String.valueOf(100.0));
+
+final HashMap<String, String> annotations = new HashMap<>();
+annotations.put("summary", "high temperature");
+annotations.put("description", "{{.alertname}}: {{.series}} is {{.value}}");
+
+alertManagerHandler.onEvent(new AlertManagerEvent(alertName, extraLabels,
annotations));
+```
+
+
+
## Maven Project Example
If you use [Maven](http://search.maven.org/), you can refer to our sample
project **trigger-example**.
diff --git a/docs/zh/UserGuide/Advanced-Features/Alerting.md
b/docs/zh/UserGuide/Advanced-Features/Alerting.md
new file mode 100644
index 0000000..2df25bc
--- /dev/null
+++ b/docs/zh/UserGuide/Advanced-Features/Alerting.md
@@ -0,0 +1,385 @@
+<!--
+
+ 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.
+
+-->
+
+# 告警
+
+## 概览
+IoTDB 告警功能预计支持两种模式:
+
+* 写入触发:用户写入原始数据到原始时间序列,每插入一条数据都会触发 `trigger` 的判断逻辑,
+若满足告警要求则发送告警到下游数据接收器,
+数据接收器再转发告警到外部终端。这种模式:
+ * 适合需要即时监控每一条数据的场景。
+ * 由于触发器中的运算会影响数据写入性能,适合对原始数据写入性能不敏感的场景。
+
+* 持续查询:用户写入原始数据到原始时间序列,
+`ContinousQuery` 定时查询原始时间序列,将查询结果写入新的时间序列,
+每一次写入触发 `trigger` 的判断逻辑,
+若满足告警要求则发送告警到下游数据接收器,
+数据接收器再转发告警到外部终端。这种模式:
+ * 适合需要定时查询数据在某一段时间内的情况的场景。
+ * 适合需要将原始数据降采样并持久化的场景。
+ * 由于定时查询几乎不影响原始时间序列的写入,适合对原始数据写入性能敏感的场景。
+
+随着 `trigger` 模块和 `sink` 模块的引入,
+目前用户使用这两个模块,配合 `AlertManager` 可以实现写入触发模式的告警。
+
+## 部署 AlertManager
+
+### 安装与运行
+#### 二进制文件
+预编译好的二进制文件可在 [这里](https://prometheus.io/download/) 下载。
+
+运行方法:
+````shell
+./alertmanager --config.file=<your_file>
+````
+
+#### Docker 镜像
+可在 [Quay.io](https://hub.docker.com/r/prom/alertmanager/)
+或 [Docker Hub](https://quay.io/repository/prometheus/alertmanager) 获得。
+
+运行方法:
+````shell
+docker run --name alertmanager -d -p 127.0.0.1:9093:9093
quay.io/prometheus/alertmanager
+````
+
+### 配置
+
+如下是一个示例,可以覆盖到大部分配置规则,详细的配置规则参见
+[这里](https://prometheus.io/docs/alerting/latest/configuration/)。
+
+示例:
+``` yaml
+# alertmanager.yml
+
+global:
+ # The smarthost and SMTP sender used for mail notifications.
+ smtp_smarthost: 'localhost:25'
+ smtp_from: '[email protected]'
+
+# The root route on which each incoming alert enters.
+route:
+ # The root route must not have any matchers as it is the entry point for
+ # all alerts. It needs to have a receiver configured so alerts that do not
+ # match any of the sub-routes are sent to someone.
+ receiver: 'team-X-mails'
+
+ # The labels by which incoming alerts are grouped together. For example,
+ # multiple alerts coming in for cluster=A and alertname=LatencyHigh would
+ # be batched into a single group.
+ #
+ # To aggregate by all possible labels use '...' as the sole label name.
+ # This effectively disables aggregation entirely, passing through all
+ # alerts as-is. This is unlikely to be what you want, unless you have
+ # a very low alert volume or your upstream notification system performs
+ # its own grouping. Example: group_by: [...]
+ group_by: ['alertname', 'cluster']
+
+ # When a new group of alerts is created by an incoming alert, wait at
+ # least 'group_wait' to send the initial notification.
+ # This way ensures that you get multiple alerts for the same group that start
+ # firing shortly after another are batched together on the first
+ # notification.
+ group_wait: 30s
+
+ # When the first notification was sent, wait 'group_interval' to send a batch
+ # of new alerts that started firing for that group.
+ group_interval: 5m
+
+ # If an alert has successfully been sent, wait 'repeat_interval' to
+ # resend them.
+ repeat_interval: 3h
+
+ # All the above attributes are inherited by all child routes and can
+ # overwritten on each.
+
+ # The child route trees.
+ routes:
+ # This routes performs a regular expression match on alert labels to
+ # catch alerts that are related to a list of services.
+ - match_re:
+ service: ^(foo1|foo2|baz)$
+ receiver: team-X-mails
+
+ # The service has a sub-route for critical alerts, any alerts
+ # that do not match, i.e. severity != critical, fall-back to the
+ # parent node and are sent to 'team-X-mails'
+ routes:
+ - match:
+ severity: critical
+ receiver: team-X-pager
+
+ - match:
+ service: files
+ receiver: team-Y-mails
+
+ routes:
+ - match:
+ severity: critical
+ receiver: team-Y-pager
+
+ # This route handles all alerts coming from a database service. If there's
+ # no team to handle it, it defaults to the DB team.
+ - match:
+ service: database
+
+ receiver: team-DB-pager
+ # Also group alerts by affected database.
+ group_by: [alertname, cluster, database]
+
+ routes:
+ - match:
+ owner: team-X
+ receiver: team-X-pager
+
+ - match:
+ owner: team-Y
+ receiver: team-Y-pager
+
+
+# Inhibition rules allow to mute a set of alerts given that another alert is
+# firing.
+# We use this to mute any warning-level notifications if the same alert is
+# already critical.
+inhibit_rules:
+- source_match:
+ severity: 'critical'
+ target_match:
+ severity: 'warning'
+ # Apply inhibition if the alertname is the same.
+ # CAUTION:
+ # If all label names listed in `equal` are missing
+ # from both the source and target alerts,
+ # the inhibition rule will apply!
+ equal: ['alertname']
+
+
+receivers:
+- name: 'team-X-mails'
+ email_configs:
+ - to: '[email protected], [email protected]'
+
+- name: 'team-X-pager'
+ email_configs:
+ - to: '[email protected]'
+ pagerduty_configs:
+ - routing_key: <team-X-key>
+
+- name: 'team-Y-mails'
+ email_configs:
+ - to: '[email protected]'
+
+- name: 'team-Y-pager'
+ pagerduty_configs:
+ - routing_key: <team-Y-key>
+
+- name: 'team-DB-pager'
+ pagerduty_configs:
+ - routing_key: <team-DB-key>
+```
+
+在后面的示例中,我们采用的配置如下:
+````yaml
+# alertmanager.yml
+
+global:
+ smtp_smarthost: ''
+ smtp_from: ''
+ smtp_auth_username: ''
+ smtp_auth_password: ''
+ smtp_require_tls: false
+
+route:
+ group_by: ['alertname']
+ group_wait: 1m
+ group_interval: 10m
+ repeat_interval: 10h
+ receiver: 'email'
+
+receivers:
+ - name: 'email'
+ email_configs:
+ - to: ''
+
+inhibit_rules:
+ - source_match:
+ severity: 'critical'
+ target_match:
+ severity: 'warning'
+ equal: ['alertname']
+````
+
+
+### API
+`AlertManager` API 分为 `v1` 和 `v2` 两个版本,当前 `AlertManager` API 版本为 `v2`
+(配置参见
+[api/v2/openapi.yaml](https://github.com/prometheus/alertmanager/blob/master/api/v2/openapi.yaml))。
+
+默认配置的前缀为 `/api/v1` 或 `/api/v2`,
+发送告警的 endpoint 为 `/api/v1/alerts` 或 `/api/v2/alerts`。
+如果用户指定了 `--web.route-prefix`,
+例如 `--web.route-prefix=/alertmanager/`,
+那么前缀将会变为 `/alertmanager/api/v1` 或 `/alertmanager/api/v2`,
+发送告警的 endpoint 变为 `/alertmanager/api/v1/alerts`
+或 `/alertmanager/api/v2/alerts`。
+
+
+## 创建 trigger
+
+### 编写 trigger 类
+
+用户通过自行创建 Java 类、编写钩子中的逻辑来定义一个触发器。
+具体配置流程以及 Sink 模块提供的 `AlertManagerSink` 相关工具类的使用方法参见 [Triggers](Triggers.md)。
+
+下面的示例创建了 `org.apache.iotdb.trigger.AlertingExample` 类,
+其 `alertManagerHandler`
+成员变量可发送告警至地址为 `http://127.0.0.1:9093/` 的 AlertManager 实例。
+
+当 `value > 100.0` 时,发送 `severity` 为 `critical` 的告警;
+当 `50.0 < value <= 100.0` 时,发送 `severity` 为 `warning` 的告警。
+
+````java
+package org.apache.iotdb.trigger;
+
+/*
+此处省略包的引入
+*/
+
+public class AlertingExample implements Trigger {
+
+ private final AlertManagerHandler alertManagerHandler = new
AlertManagerHandler();
+
+ private final AlertManagerConfiguration alertManagerConfiguration =
+ new AlertManagerConfiguration("http://127.0.0.1:9093/api/v2/alerts");
+
+ private String alertname;
+
+ private final HashMap<String, String> labels = new HashMap<>();
+
+ private final HashMap<String, String> annotations = new HashMap<>();
+
+ @Override
+ public void onCreate(TriggerAttributes attributes) throws Exception {
+ alertManagerHandler.open(alertManagerConfiguration);
+
+ alertname = "alert_test";
+
+ labels.put("series", "root.ln.wf01.wt01.temperature");
+ labels.put("value", "");
+ labels.put("severity", "");
+
+ annotations.put("summary", "high temperature");
+ annotations.put("description", "{{.alertname}}: {{.series}} is
{{.value}}");
+ }
+
+ @Override
+ public void onDrop() throws IOException {
+ alertManagerHandler.close();
+ }
+
+ @Override
+ public void onStart() {
+ alertManagerHandler.open(alertManagerConfiguration);
+ }
+
+ @Override
+ public void onStop() throws Exception {
+ alertManagerHandler.close();
+ }
+
+ @Override
+ public Double fire(long timestamp, Double value) throws Exception {
+ if (value > 100.0) {
+ labels.put("value", String.valueOf(value));
+ labels.put("severity", "critical");
+ AlertManagerEvent alertManagerEvent = new AlertManagerEvent(alertname,
labels, annotations);
+ alertManagerHandler.onEvent(alertManagerEvent);
+ } else if (value > 50.0) {
+ labels.put("value", String.valueOf(value));
+ labels.put("severity", "warning");
+ AlertManagerEvent alertManagerEvent = new AlertManagerEvent(alertname,
labels, annotations);
+ alertManagerHandler.onEvent(alertManagerEvent);
+ }
+
+ return value;
+ }
+
+ @Override
+ public double[] fire(long[] timestamps, double[] values) throws Exception {
+ for (double value : values) {
+ if (value > 100.0) {
+ labels.put("value", String.valueOf(value));
+ labels.put("severity", "critical");
+ AlertManagerEvent alertManagerEvent = new AlertManagerEvent(alertname,
labels, annotations);
+ alertManagerHandler.onEvent(alertManagerEvent);
+ } else if (value > 50.0) {
+ labels.put("value", String.valueOf(value));
+ labels.put("severity", "warning");
+ AlertManagerEvent alertManagerEvent = new AlertManagerEvent(alertname,
labels, annotations);
+ alertManagerHandler.onEvent(alertManagerEvent);
+ }
+ }
+ return values;
+ }
+}
+
+````
+
+### 创建 trigger
+
+如下的 sql 语句在 `root.ln.wf01.wt01.temperature`
+时间序列上注册了名为 `root-ln-wf01-wt01-alert`、
+运行逻辑由 `org.apache.iotdb.trigger.AlertingExample`
+类定义的触发器。
+
+``` sql
+ CREATE TRIGGER root-ln-wf01-wt01-alert
+ AFTER INSERT
+ ON root.ln.wf01.wt01.temperature
+ AS "org.apache.iotdb.trigger.AlertingExample"
+```
+
+## 写入数据
+
+当我们完成 AlertManager 的部署和启动、Trigger 的创建,
+可以通过向时间序列写入数据来测试告警功能。
+
+``` sql
+INSERT INTO root.ln.wf01.wt01(timestamp, temperature) VALUES (1, 0);
+INSERT INTO root.ln.wf01.wt01(timestamp, temperature) VALUES (2, 30);
+INSERT INTO root.ln.wf01.wt01(timestamp, temperature) VALUES (3, 60);
+INSERT INTO root.ln.wf01.wt01(timestamp, temperature) VALUES (4, 90);
+INSERT INTO root.ln.wf01.wt01(timestamp, temperature) VALUES (5, 120);
+```
+
+执行完上述写入语句后,可以收到告警邮件。由于我们的 `AlertManager` 配置中设定 `severity` 为 `critical` 的告警
+会抑制 `severity` 为 `warning` 的告警,我们收到的告警邮件中只包含写入
+`(5, 120)` 后触发的告警。
+
+<img alt="alerting"
src="https://user-images.githubusercontent.com/34649843/115957896-a9791080-a537-11eb-9962-541412bdcee6.png">
+
+
+
+
+
+
+
diff --git a/docs/zh/UserGuide/Advanced-Features/Triggers.md
b/docs/zh/UserGuide/Advanced-Features/Triggers.md
index 501d7ad..be66f82 100644
--- a/docs/zh/UserGuide/Advanced-Features/Triggers.md
+++ b/docs/zh/UserGuide/Advanced-Features/Triggers.md
@@ -585,6 +585,113 @@ for (int i = 0; i < 100; ++i) {
+#### AlertManagerSink
+
+触发器可以使用`AlertManagerSink` 向 AlertManager 发送消息。
+
+`AlertManagerConfiguration` 的构造需传入 AlertManager 的发送告警的 endpoint。
+```java
+AlertManagerConfiguration(String endpoint);
+```
+
+`AlertManagerEvent` 提供三种构造函数:
+```java
+AlertManagerEvent(String alertname);
+AlertManagerEvent(String alertname, Map<String, String> extraLabels);
+AlertManagerEvent(String alertname, Map<String, String> extraLabels,
Map<String, String> annotations);
+```
+其中:
+* `alertname` 是必传参数,用于标识一个 `alert`,`alertname` 字段可用于 `AlertManager`
发送告警时的分组和消重。
+* `extraLabels` 可选传,在后台与 `alertname` 组合成 `labels` 一起标识一个 `alert`,可用于
`AlertManager` 发送告警时的分组和消重。
+* `annotations` 可选传,它的 value 值可使用 Go 语言模板风格的 `{{.<label_key>}}`,
+ `{{.<label_key>}}` 在最终生成消息时会被替换为 `labels[<label_key>]`。
+* `labels` 和 `annotations` 会被解析成 json 字符串发送给 `AlertManager`:
+```json
+{
+ "labels": {
+ "alertname": "<requiredAlertName>",
+ "<labelname>": "<labelvalue>",
+ ...
+ },
+ "annotations": {
+ "<labelname>": "<labelvalue>",
+ ...
+ }
+}
+```
+
+调用 `AlertManagerHandler` 的 `onEvent(AlertManagerEvent event)` 方法发送一个告警。
+
+
+
+**使用示例 1:**
+
+只传 `alertname`。
+
+```java
+AlertManagerHandler alertManagerHandler = new AlertManagerHandler();
+
+alertManagerHandler.open(new
AlertManagerConfiguration("http://127.0.0.1:9093/api/v1/alerts"));
+
+final String alertName = "test0";
+
+AlertManagerEvent alertManagerEvent = new AlertManagerEvent(alertName);
+
+alertManagerHandler.onEvent(alertManagerEvent);
+```
+
+
+
+**使用示例 2:**
+
+传入 `alertname` 和 `extraLabels`。
+
+```java
+AlertManagerHandler alertManagerHandler = new AlertManagerHandler();
+
+alertManagerHandler.open(new
AlertManagerConfiguration("http://127.0.0.1:9093/api/v1/alerts"));
+
+final String alertName = "test1";
+
+final HashMap<String, String> extraLabels = new HashMap<>();
+extraLabels.put("severity", "critical");
+extraLabels.put("series", "root.ln.wt01.wf01.temperature");
+extraLabels.put("value", String.valueOf(100.0));
+
+AlertManagerEvent alertManagerEvent = new AlertManagerEvent(alertName,
extraLabels);
+
+alertManagerHandler.onEvent(alertManagerEvent);
+```
+
+
+
+**使用示例 3:**
+
+传入 `alertname`, `extraLabels` 和 `annotations` 。
+
+最终 `description` 字段的值会被解析为 `test2: root.ln.wt01.wf01.temperature is 100.0`。
+
+```java
+AlertManagerHandler alertManagerHandler = new AlertManagerHandler();
+
+alertManagerHandler.open(new
AlertManagerConfiguration("http://127.0.0.1:9093/api/v1/alerts"));
+
+final String alertName = "test2";
+
+final HashMap<String, String> extraLabels = new HashMap<>();
+extraLabels.put("severity", "critical");
+extraLabels.put("series", "root.ln.wt01.wf01.temperature");
+extraLabels.put("value", String.valueOf(100.0));
+
+final HashMap<String, String> annotations = new HashMap<>();
+annotations.put("summary", "high temperature");
+annotations.put("description", "{{.alertname}}: {{.series}} is {{.value}}");
+
+alertManagerHandler.onEvent(new AlertManagerEvent(alertName, extraLabels,
annotations));
+```
+
+
+
## 完整的Maven示例项目
如果您使用[Maven](http://search.maven.org/),可以参考我们编写的示例项目**trigger-example**。
diff --git
a/example/trigger/src/main/java/org/apache/iotdb/trigger/AlertingExample.java
b/example/trigger/src/main/java/org/apache/iotdb/trigger/AlertingExample.java
new file mode 100644
index 0000000..fb1b5b1
--- /dev/null
+++
b/example/trigger/src/main/java/org/apache/iotdb/trigger/AlertingExample.java
@@ -0,0 +1,107 @@
+/*
+ * 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.iotdb.trigger;
+
+import org.apache.iotdb.db.engine.trigger.api.Trigger;
+import org.apache.iotdb.db.engine.trigger.api.TriggerAttributes;
+import org.apache.iotdb.db.sink.alertmanager.AlertManagerConfiguration;
+import org.apache.iotdb.db.sink.alertmanager.AlertManagerEvent;
+import org.apache.iotdb.db.sink.alertmanager.AlertManagerHandler;
+
+import java.io.IOException;
+import java.util.HashMap;
+
+public class AlertingExample implements Trigger {
+
+ private final AlertManagerHandler alertManagerHandler = new
AlertManagerHandler();
+
+ private final AlertManagerConfiguration alertManagerConfiguration =
+ new AlertManagerConfiguration("http://127.0.0.1:9093/api/v2/alerts");
+
+ private String alertname;
+
+ private final HashMap<String, String> labels = new HashMap<>();
+
+ private final HashMap<String, String> annotations = new HashMap<>();
+
+ @Override
+ public void onCreate(TriggerAttributes attributes) throws Exception {
+ alertManagerHandler.open(alertManagerConfiguration);
+
+ alertname = "alert_test";
+
+ labels.put("series", "root.ln.wf01.wt01.temperature");
+ labels.put("value", "");
+ labels.put("severity", "");
+
+ annotations.put("summary", "high temperature");
+ annotations.put("description", "{{.alertname}}: {{.series}} is
{{.value}}");
+ }
+
+ @Override
+ public void onDrop() throws IOException {
+ alertManagerHandler.close();
+ }
+
+ @Override
+ public void onStart() {
+ alertManagerHandler.open(alertManagerConfiguration);
+ }
+
+ @Override
+ public void onStop() throws Exception {
+ alertManagerHandler.close();
+ }
+
+ @Override
+ public Double fire(long timestamp, Double value) throws Exception {
+ if (value > 100.0) {
+ labels.put("value", String.valueOf(value));
+ labels.put("severity", "critical");
+ AlertManagerEvent alertManagerEvent = new AlertManagerEvent(alertname,
labels, annotations);
+ alertManagerHandler.onEvent(alertManagerEvent);
+ } else if (value > 50.0) {
+ labels.put("value", String.valueOf(value));
+ labels.put("severity", "warning");
+ AlertManagerEvent alertManagerEvent = new AlertManagerEvent(alertname,
labels, annotations);
+ alertManagerHandler.onEvent(alertManagerEvent);
+ }
+
+ return value;
+ }
+
+ @Override
+ public double[] fire(long[] timestamps, double[] values) throws Exception {
+ for (double value : values) {
+ if (value > 100.0) {
+ labels.put("value", String.valueOf(value));
+ labels.put("severity", "critical");
+ AlertManagerEvent alertManagerEvent = new AlertManagerEvent(alertname,
labels, annotations);
+ alertManagerHandler.onEvent(alertManagerEvent);
+ } else if (value > 50.0) {
+ labels.put("value", String.valueOf(value));
+ labels.put("severity", "warning");
+ AlertManagerEvent alertManagerEvent = new AlertManagerEvent(alertname,
labels, annotations);
+ alertManagerHandler.onEvent(alertManagerEvent);
+ }
+ }
+ return values;
+ }
+}
diff --git a/server/pom.xml b/server/pom.xml
index 0df9993..08944e2 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -207,6 +207,12 @@
<groupId>org.fusesource.mqtt-client</groupId>
<artifactId>mqtt-client</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient</artifactId>
+ <version>4.3.5</version>
+ <scope>compile</scope>
+ </dependency>
</dependencies>
<build>
<plugins>
diff --git
a/server/src/main/java/org/apache/iotdb/db/sink/alertmanager/AlertManagerConfiguration.java
b/server/src/main/java/org/apache/iotdb/db/sink/alertmanager/AlertManagerConfiguration.java
new file mode 100644
index 0000000..3ed4c37
--- /dev/null
+++
b/server/src/main/java/org/apache/iotdb/db/sink/alertmanager/AlertManagerConfiguration.java
@@ -0,0 +1,35 @@
+/*
+ * 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.iotdb.db.sink.alertmanager;
+
+import org.apache.iotdb.db.sink.api.Configuration;
+
+public class AlertManagerConfiguration implements Configuration {
+
+ private final String endpoint;
+
+ public AlertManagerConfiguration(String endpoint) {
+ this.endpoint = endpoint;
+ }
+
+ public String getEndpoint() {
+ return endpoint;
+ }
+}
diff --git
a/server/src/main/java/org/apache/iotdb/db/sink/alertmanager/AlertManagerEvent.java
b/server/src/main/java/org/apache/iotdb/db/sink/alertmanager/AlertManagerEvent.java
new file mode 100644
index 0000000..d26de46
--- /dev/null
+++
b/server/src/main/java/org/apache/iotdb/db/sink/alertmanager/AlertManagerEvent.java
@@ -0,0 +1,123 @@
+/*
+ * 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.iotdb.db.sink.alertmanager;
+
+import org.apache.iotdb.db.sink.api.Event;
+import org.apache.iotdb.db.sink.exception.SinkException;
+
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+
+import java.lang.reflect.Type;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class AlertManagerEvent implements Event {
+
+ private static final String PARAMETER_NULL_ERROR_STR = "parameter null
error";
+
+ private static final String ALERTNAME_KEY = "alertname";
+
+ private final Map<String, String> labels;
+
+ private final Map<String, String> annotations;
+
+ private static final Pattern pattern = Pattern.compile("\\{\\{\\.\\w+}}");
+
+ public AlertManagerEvent(String alertname) throws SinkException {
+ if (alertname == null) {
+ throw new SinkException(PARAMETER_NULL_ERROR_STR);
+ }
+ this.labels = new HashMap<>();
+ this.labels.put(ALERTNAME_KEY, alertname);
+ this.annotations = null;
+ }
+
+ public AlertManagerEvent(String alertname, Map<String, String> extraLabels)
throws SinkException {
+ if (alertname == null || extraLabels == null) {
+ throw new SinkException(PARAMETER_NULL_ERROR_STR);
+ }
+ this.labels = extraLabels;
+ this.labels.put(ALERTNAME_KEY, alertname);
+ this.annotations = null;
+ }
+
+ public AlertManagerEvent(
+ String alertname, Map<String, String> extraLabels, Map<String, String>
annotations)
+ throws SinkException {
+ if (alertname == null || extraLabels == null || annotations == null) {
+ throw new SinkException(PARAMETER_NULL_ERROR_STR);
+ }
+
+ this.labels = extraLabels;
+ this.labels.put(ALERTNAME_KEY, alertname);
+ this.annotations = new HashMap<>();
+
+ for (Map.Entry<String, String> entry : annotations.entrySet()) {
+ this.annotations.put(entry.getKey(), fillTemplate(this.labels,
entry.getValue()));
+ }
+ }
+
+ public Map<String, String> getAnnotations() {
+ return annotations;
+ }
+
+ public Map<String, String> getLabels() {
+ return labels;
+ }
+
+ public String toJsonString() {
+ Gson gson = new Gson();
+ Type gsonType = new TypeToken<Map>() {}.getType();
+
+ StringBuilder sb = new StringBuilder();
+ sb.append("{\"labels\":");
+
+ String labelsString = gson.toJson(this.labels, gsonType);
+ sb.append(labelsString);
+
+ if (this.annotations != null) {
+ String annotationsString = gson.toJson(this.annotations, gsonType);
+ sb.append(",");
+ sb.append("\"annotations\":");
+ sb.append(annotationsString);
+ }
+ sb.append("}");
+ return sb.toString();
+ }
+
+ private static String fillTemplate(Map<String, String> map, String template)
{
+ if (template == null || map == null) {
+ return null;
+ }
+ StringBuffer sb = new StringBuffer();
+ Matcher m = pattern.matcher(template);
+ while (m.find()) {
+ String param = m.group();
+ String key = param.substring(3, param.length() - 2).trim();
+ String value = map.get(key);
+ m.appendReplacement(sb, value == null ? "" : value);
+ }
+ m.appendTail(sb);
+ return sb.toString();
+ }
+}
diff --git
a/server/src/main/java/org/apache/iotdb/db/sink/alertmanager/AlertManagerHandler.java
b/server/src/main/java/org/apache/iotdb/db/sink/alertmanager/AlertManagerHandler.java
new file mode 100644
index 0000000..600bb72
--- /dev/null
+++
b/server/src/main/java/org/apache/iotdb/db/sink/alertmanager/AlertManagerHandler.java
@@ -0,0 +1,88 @@
+/*
+ * 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.iotdb.db.sink.alertmanager;
+
+import org.apache.iotdb.db.sink.api.Handler;
+import org.apache.iotdb.db.sink.exception.SinkException;
+
+import org.apache.http.HttpStatus;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+
+import java.io.IOException;
+
+public class AlertManagerHandler
+ implements Handler<
+ org.apache.iotdb.db.sink.alertmanager.AlertManagerConfiguration,
+ org.apache.iotdb.db.sink.alertmanager.AlertManagerEvent> {
+
+ private HttpPost request;
+
+ private static CloseableHttpClient client;
+
+ private static int refCount = 0;
+
+ private static synchronized void closeClient() throws IOException {
+ if (--refCount == 0) {
+ client.close();
+ }
+ }
+
+ private static synchronized void openClient() {
+ if (refCount++ == 0) {
+ client = HttpClients.createDefault();
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ closeClient();
+ }
+
+ @Override
+ public void
open(org.apache.iotdb.db.sink.alertmanager.AlertManagerConfiguration
configuration) {
+ if (this.request == null) {
+ this.request = new HttpPost(configuration.getEndpoint());
+ request.setHeader("Accept", "application/json");
+ request.setHeader("Content-type", "application/json");
+ }
+
+ openClient();
+ }
+
+ @Override
+ public void onEvent(AlertManagerEvent event) throws Exception {
+
+ String json = "[" + event.toJsonString() + "]";
+
+ request.setEntity(new StringEntity(json));
+
+ CloseableHttpResponse response = client.execute(request);
+
+ if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
+ throw new SinkException(response.getStatusLine().toString());
+ }
+
+ response.close();
+ }
+}
diff --git
a/server/src/test/java/org/apache/iotdb/db/sink/AlertManagerTest.java
b/server/src/test/java/org/apache/iotdb/db/sink/AlertManagerTest.java
new file mode 100644
index 0000000..2721abb
--- /dev/null
+++ b/server/src/test/java/org/apache/iotdb/db/sink/AlertManagerTest.java
@@ -0,0 +1,336 @@
+/*
+ * 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.iotdb.db.sink;
+
+import org.apache.iotdb.db.sink.alertmanager.AlertManagerConfiguration;
+import org.apache.iotdb.db.sink.alertmanager.AlertManagerEvent;
+import org.apache.iotdb.db.sink.alertmanager.AlertManagerHandler;
+
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
+import org.junit.Test;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.InetSocketAddress;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+
+import static org.junit.Assert.assertEquals;
+
+public class AlertManagerTest {
+
+ class TestHandler implements HttpHandler {
+
+ String correctRequest;
+
+ public TestHandler(String correctRequest) {
+ this.correctRequest = correctRequest;
+ }
+
+ @Override
+ public void handle(HttpExchange exchange) throws IOException {
+ InputStreamReader isr =
+ new InputStreamReader(exchange.getRequestBody(),
StandardCharsets.UTF_8);
+ BufferedReader br = new BufferedReader(isr);
+ String query = br.readLine();
+
+ assertEquals(correctRequest, query);
+
+ byte[] response = "{\"success\": true}".getBytes();
+ exchange.sendResponseHeaders(HttpURLConnection.HTTP_OK, response.length);
+ exchange.getResponseBody().write(response);
+ exchange.close();
+ }
+ }
+
+ @Test
+ public void alertmanagerTest0() throws Exception {
+
+ HttpServer httpServer = HttpServer.create(new InetSocketAddress(9093), 0);
+ httpServer.createContext(
+ "/api/v2/alerts", new
TestHandler("[{\"labels\":{\"alertname\":\"test0\"}}]"));
+
+ httpServer.start();
+
+ AlertManagerConfiguration alertManagerConfiguration =
+ new AlertManagerConfiguration("http://127.0.0.1:9093/api/v2/alerts");
+ AlertManagerHandler alertManagerHandler = new AlertManagerHandler();
+
+ alertManagerHandler.open(alertManagerConfiguration);
+
+ String alertName = "test0";
+
+ AlertManagerEvent alertManagerEvent = new AlertManagerEvent(alertName);
+
+ alertManagerHandler.onEvent(alertManagerEvent);
+
+ assertEquals("test0", alertManagerEvent.getLabels().get("alertname"));
+
+ alertManagerHandler.close();
+
+ httpServer.stop(0);
+ }
+
+ @Test
+ public void alertmanagerTest1() throws Exception {
+
+ HttpServer httpServer = HttpServer.create(new InetSocketAddress(9093), 0);
+ httpServer.createContext(
+ "/api/v2/alerts",
+ new TestHandler(
+ "[{\"labels\":"
+ + "{\"severity\":\"critical\","
+ + "\"series\":\"root.ln.wt01.wf01.temperature\","
+ + "\"alertname\":\"test1\","
+ + "\"value\":\"100.0\"}}]"));
+
+ httpServer.start();
+
+ AlertManagerConfiguration alertManagerConfiguration =
+ new AlertManagerConfiguration("http://127.0.0.1:9093/api/v2/alerts");
+ AlertManagerHandler alertManagerHandler = new AlertManagerHandler();
+
+ alertManagerHandler.open(alertManagerConfiguration);
+
+ String alertName = "test1";
+
+ HashMap<String, String> extraLabels = new HashMap<>();
+ extraLabels.put("severity", "critical");
+ extraLabels.put("series", "root.ln.wt01.wf01.temperature");
+ extraLabels.put("value", String.valueOf(100.0));
+
+ AlertManagerEvent alertManagerEvent = new AlertManagerEvent(alertName,
extraLabels);
+
+ alertManagerHandler.onEvent(alertManagerEvent);
+
+ assertEquals("test1", alertManagerEvent.getLabels().get("alertname"));
+ assertEquals("critical", alertManagerEvent.getLabels().get("severity"));
+ assertEquals("root.ln.wt01.wf01.temperature",
alertManagerEvent.getLabels().get("series"));
+ assertEquals(String.valueOf(100.0),
alertManagerEvent.getLabels().get("value"));
+
+ alertManagerHandler.close();
+
+ httpServer.stop(0);
+ }
+
+ @Test
+ public void alertmanagerTest2() throws Exception {
+
+ HttpServer httpServer = HttpServer.create(new InetSocketAddress(9093), 0);
+ httpServer.createContext(
+ "/api/v2/alerts",
+ new TestHandler(
+ "[{\"labels\":"
+ + "{\"severity\":\"critical\","
+ + "\"series\":\"root.ln.wt01.wf01.temperature\","
+ + "\"alertname\":\"test2\","
+ + "\"value\":\"100.0\"},"
+ + "\"annotations\":"
+ + "{\"summary\":\"high temperature\","
+ + "\"description\":\"test2: root.ln.wt01.wf01.temperature is
100.0\"}}]"));
+
+ httpServer.start();
+
+ AlertManagerConfiguration alertManagerConfiguration =
+ new AlertManagerConfiguration("http://127.0.0.1:9093/api/v2/alerts");
+ AlertManagerHandler alertManagerHandler = new AlertManagerHandler();
+
+ alertManagerHandler.open(alertManagerConfiguration);
+
+ String alertName = "test2";
+
+ HashMap<String, String> extraLabels = new HashMap<>();
+ extraLabels.put("severity", "critical");
+ extraLabels.put("series", "root.ln.wt01.wf01.temperature");
+ extraLabels.put("value", String.valueOf(100.0));
+
+ HashMap<String, String> annotations = new HashMap<>();
+ annotations.put("summary", "high temperature");
+ annotations.put("description", "{{.alertname}}: {{.series}} is
{{.value}}");
+
+ AlertManagerEvent alertManagerEvent =
+ new AlertManagerEvent(alertName, extraLabels, annotations);
+
+ alertManagerHandler.onEvent(alertManagerEvent);
+
+ assertEquals("test2", alertManagerEvent.getLabels().get("alertname"));
+ assertEquals("critical", alertManagerEvent.getLabels().get("severity"));
+ assertEquals("root.ln.wt01.wf01.temperature",
alertManagerEvent.getLabels().get("series"));
+ assertEquals(String.valueOf(100.0),
alertManagerEvent.getLabels().get("value"));
+
+ assertEquals("high temperature",
alertManagerEvent.getAnnotations().get("summary"));
+ assertEquals(
+ "test2: root.ln.wt01.wf01.temperature is 100.0",
+ alertManagerEvent.getAnnotations().get("description"));
+
+ alertManagerHandler.close();
+
+ httpServer.stop(0);
+ }
+
+ @Test
+ public void multiAlertmanagerReopenTest() throws Exception {
+
+ HttpServer httpServer = HttpServer.create(new InetSocketAddress(9093), 0);
+ httpServer.createContext(
+ "/api/v2/alerts",
+ new TestHandler(
+ "[{\"labels\":"
+ + "{\"severity\":\"critical\","
+ + "\"series\":\"root.ln.wt01.wf01.temperature\","
+ + "\"alertname\":\"test1\","
+ + "\"value\":\"100.0\"}}]"));
+
+ httpServer.start();
+
+ HttpServer httpServer2 = HttpServer.create(new InetSocketAddress(9094), 0);
+ httpServer2.createContext(
+ "/api/v2/alerts",
+ new TestHandler(
+ "[{\"labels\":"
+ + "{\"severity\":\"critical\","
+ + "\"series\":\"root.ln.wt01.wf01.temperature\","
+ + "\"alertname\":\"test1\","
+ + "\"value\":\"100.0\"}}]"));
+
+ httpServer2.start();
+
+ HttpServer httpServer3 = HttpServer.create(new InetSocketAddress(9095), 0);
+ httpServer3.createContext(
+ "/api/v2/alerts",
+ new TestHandler(
+ "[{\"labels\":"
+ + "{\"severity\":\"critical\","
+ + "\"series\":\"root.ln.wt01.wf01.temperature\","
+ + "\"alertname\":\"test1\","
+ + "\"value\":\"100.0\"}}]"));
+
+ httpServer3.start();
+
+ AlertManagerConfiguration alertManagerConfiguration =
+ new AlertManagerConfiguration("http://127.0.0.1:9093/api/v2/alerts");
+
+ AlertManagerConfiguration alertManagerConfiguration1 =
+ new AlertManagerConfiguration("http://127.0.0.1:9094/api/v2/alerts");
+
+ AlertManagerConfiguration alertManagerConfiguration2 =
+ new AlertManagerConfiguration("http://127.0.0.1:9095/api/v2/alerts");
+
+ String alertName = "test1";
+
+ HashMap<String, String> extraLabels = new HashMap<>();
+ extraLabels.put("severity", "critical");
+ extraLabels.put("series", "root.ln.wt01.wf01.temperature");
+ extraLabels.put("value", String.valueOf(100.0));
+
+ AlertManagerEvent alertManagerEvent = new AlertManagerEvent(alertName,
extraLabels);
+
+ AlertManagerHandler alertManagerHandler = new AlertManagerHandler();
+ alertManagerHandler.open(alertManagerConfiguration);
+ alertManagerHandler.onEvent(alertManagerEvent);
+ alertManagerHandler.close();
+
+ AlertManagerHandler alertManagerHandler1 = new AlertManagerHandler();
+ alertManagerHandler1.open(alertManagerConfiguration1);
+ alertManagerHandler1.onEvent(alertManagerEvent);
+ alertManagerHandler1.close();
+
+ AlertManagerHandler alertManagerHandler2 = new AlertManagerHandler();
+ alertManagerHandler2.open(alertManagerConfiguration2);
+
+ alertManagerHandler1.open(alertManagerConfiguration1);
+
+ alertManagerHandler1.onEvent(alertManagerEvent);
+
+ alertManagerHandler2.onEvent(alertManagerEvent);
+
+ alertManagerHandler2.close();
+
+ alertManagerHandler1.close();
+
+ httpServer.stop(0);
+ httpServer2.stop(0);
+ httpServer3.stop(0);
+ }
+
+ @Test
+ public void alertmanagerEventToJsonTest0() throws Exception {
+
+ String alertName = "test0";
+
+ AlertManagerEvent alertManagerEvent = new AlertManagerEvent(alertName);
+
+ assertEquals("{\"labels\":{\"alertname\":\"test0\"}}",
alertManagerEvent.toJsonString());
+ }
+
+ @Test
+ public void alertmanagerEventToJsonTest1() throws Exception {
+
+ String alertName = "test1";
+
+ HashMap<String, String> extraLabels = new HashMap<>();
+ extraLabels.put("severity", "critical");
+ extraLabels.put("series", "root.ln.wt01.wf01.temperature");
+ extraLabels.put("value", String.valueOf(100.0));
+
+ AlertManagerEvent alertManagerEvent = new AlertManagerEvent(alertName,
extraLabels);
+
+ assertEquals(
+ "{\"labels\":"
+ + "{\"severity\":\"critical\","
+ + "\"series\":\"root.ln.wt01.wf01.temperature\","
+ + "\"alertname\":\"test1\","
+ + "\"value\":\"100.0\"}}",
+ alertManagerEvent.toJsonString());
+ }
+
+ @Test
+ public void alertmanagerEventToJsonTest2() throws Exception {
+
+ String alertName = "test2";
+
+ HashMap<String, String> extraLabels = new HashMap<>();
+ extraLabels.put("severity", "critical");
+ extraLabels.put("series", "root.ln.wt01.wf01.temperature");
+ extraLabels.put("value", String.valueOf(100.0));
+
+ HashMap<String, String> annotations = new HashMap<>();
+ annotations.put("summary", "high temperature");
+ annotations.put("description", "{{.alertname}}: {{.series}} is
{{.value}}");
+
+ AlertManagerEvent alertManagerEvent =
+ new AlertManagerEvent(alertName, extraLabels, annotations);
+
+ assertEquals(
+ "{\"labels\":"
+ + "{\"severity\":\"critical\","
+ + "\"series\":\"root.ln.wt01.wf01.temperature\","
+ + "\"alertname\":\"test2\","
+ + "\"value\":\"100.0\"},"
+ + "\"annotations\":"
+ + "{\"summary\":\"high temperature\","
+ + "\"description\":\"test2: root.ln.wt01.wf01.temperature is
100.0\"}}",
+ alertManagerEvent.toJsonString());
+ }
+}