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());
+  }
+}

Reply via email to