You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by wu...@apache.org on 2021/04/25 16:01:45 UTC

[skywalking] branch master updated: feature:Support alarm to WeLink (#6794)

This is an automated email from the ASF dual-hosted git repository.

wusheng pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/skywalking.git


The following commit(s) were added to refs/heads/master by this push:
     new 1aa5f26  feature:Support alarm to WeLink (#6794)
1aa5f26 is described below

commit 1aa5f2682d88145718f0ad5d893107cea26f46a7
Author: 高云峰 <ga...@gmail.com>
AuthorDate: Mon Apr 26 00:01:19 2021 +0800

    feature:Support alarm to WeLink (#6794)
---
 CHANGES.md                                         |   1 +
 docs/en/setup/backend/backend-alarm.md             |  18 +++
 .../core/alarm/provider/AlarmRulesWatcher.java     |   4 +
 .../server/core/alarm/provider/NotifyHandler.java  |   2 +
 .../oap/server/core/alarm/provider/Rules.java      |   2 +
 .../server/core/alarm/provider/RulesReader.java    |  30 ++++
 .../alarm/provider/welink/WeLinkHookCallback.java  | 172 +++++++++++++++++++++
 .../core/alarm/provider/welink/WeLinkSettings.java |  60 +++++++
 .../core/alarm/provider/AlarmRulesWatcherTest.java |   1 +
 .../provider/welink/WeLinkHookCallbackTest.java    | 154 ++++++++++++++++++
 .../src/test/resources/alarm-settings.yml          |  13 ++
 .../src/main/resources/alarm-settings.yml          |  13 ++
 12 files changed, 470 insertions(+)

diff --git a/CHANGES.md b/CHANGES.md
index da04901..282e97a 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -23,6 +23,7 @@ Release Notes.
 * Fix K8s monitoring the incorrect metrics calculate. 
 * Loop alarm into event system.
 * Support alarm tags.
+* Support WeLink as a channel of alarm notification.
 
 #### UI
 * Add logo for kong plugin.
diff --git a/docs/en/setup/backend/backend-alarm.md b/docs/en/setup/backend/backend-alarm.md
index 21535ac..21ffb19 100644
--- a/docs/en/setup/backend/backend-alarm.md
+++ b/docs/en/setup/backend/backend-alarm.md
@@ -281,6 +281,24 @@ feishuHooks:
       secret: dummysecret
 ```
 
+## WeLink Hook
+To do this you need to follow the [WeLink Webhooks guide](https://open.welink.huaweicloud.com/apiexplorer/#/apiexplorer?type=internal&method=POST&path=/welinkim/v1/im-service/chat/group-chat) and create new Webhooks.
+The alarm message will send through HTTP post by `application/json` content type if you configured WeLink Webhooks as following:
+```yml
+welinkHooks:
+  textTemplate: "Apache SkyWalking Alarm: \n %s."
+  webhooks:
+    # you may find your own client_id and client_secret in your app, below are dummy, need to change.
+    - client_id: "dummy_client_id"
+      client_secret: dummy_secret_key
+      access_token_url: https://open.welink.huaweicloud.com/api/auth/v2/tickets
+      message_url: https://open.welink.huaweicloud.com/api/welinkim/v1/im-service/chat/group-chat
+      # if you send to multi group at a time, separate group_ids with commas, e.g. "123xx","456xx"
+      group_ids: "dummy_group_id"
+      # make a name you like for the robot, it will display in group
+      robot_name: robot
+```
+
 ## Update the settings dynamically
 Since 6.5.0, the alarm settings can be updated dynamically at runtime by [Dynamic Configuration](dynamic-config.md),
 which will override the settings in `alarm-settings.yml`.
diff --git a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmRulesWatcher.java b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmRulesWatcher.java
index 6884905..6b1ea47 100644
--- a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmRulesWatcher.java
+++ b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmRulesWatcher.java
@@ -35,6 +35,7 @@ import org.apache.skywalking.oap.server.core.alarm.provider.feishu.FeishuSetting
 import org.apache.skywalking.oap.server.core.alarm.provider.grpc.GRPCAlarmSetting;
 import org.apache.skywalking.oap.server.core.alarm.provider.slack.SlackSettings;
 import org.apache.skywalking.oap.server.core.alarm.provider.wechat.WechatSettings;
+import org.apache.skywalking.oap.server.core.alarm.provider.welink.WeLinkSettings;
 import org.apache.skywalking.oap.server.library.module.ModuleProvider;
 
 /**
@@ -139,4 +140,7 @@ public class AlarmRulesWatcher extends ConfigChangeWatcher {
         return this.rules.getFeishus();
     }
 
+    public WeLinkSettings getWeLinkSettings() {
+        return this.rules.getWelinks();
+    }
 }
diff --git a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/NotifyHandler.java b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/NotifyHandler.java
index 69772ab..932a192 100644
--- a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/NotifyHandler.java
+++ b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/NotifyHandler.java
@@ -36,6 +36,7 @@ import org.apache.skywalking.oap.server.core.alarm.provider.feishu.FeishuHookCal
 import org.apache.skywalking.oap.server.core.alarm.provider.grpc.GRPCCallback;
 import org.apache.skywalking.oap.server.core.alarm.provider.slack.SlackhookCallback;
 import org.apache.skywalking.oap.server.core.alarm.provider.wechat.WechatHookCallback;
+import org.apache.skywalking.oap.server.core.alarm.provider.welink.WeLinkHookCallback;
 import org.apache.skywalking.oap.server.core.analysis.IDManager;
 import org.apache.skywalking.oap.server.core.analysis.metrics.Metrics;
 import org.apache.skywalking.oap.server.core.analysis.metrics.MetricsMetaInfo;
@@ -170,6 +171,7 @@ public class NotifyHandler implements MetricsNotify {
         allCallbacks.add(new DingtalkHookCallback(alarmRulesWatcher));
         allCallbacks.add(new FeishuHookCallback(alarmRulesWatcher));
         allCallbacks.add(new EventHookCallback(this.manager));
+        allCallbacks.add(new WeLinkHookCallback(alarmRulesWatcher));
         core.start(allCallbacks);
     }
 }
diff --git a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/Rules.java b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/Rules.java
index 1e93acd..016d46f 100644
--- a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/Rules.java
+++ b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/Rules.java
@@ -28,6 +28,7 @@ import org.apache.skywalking.oap.server.core.alarm.provider.feishu.FeishuSetting
 import org.apache.skywalking.oap.server.core.alarm.provider.grpc.GRPCAlarmSetting;
 import org.apache.skywalking.oap.server.core.alarm.provider.slack.SlackSettings;
 import org.apache.skywalking.oap.server.core.alarm.provider.wechat.WechatSettings;
+import org.apache.skywalking.oap.server.core.alarm.provider.welink.WeLinkSettings;
 
 @Setter
 @Getter
@@ -41,6 +42,7 @@ public class Rules {
     private List<CompositeAlarmRule> compositeRules;
     private DingtalkSettings dingtalks;
     private FeishuSettings feishus;
+    private WeLinkSettings welinks;
 
     public Rules() {
         this.rules = new ArrayList<>();
diff --git a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/RulesReader.java b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/RulesReader.java
index 2bf138a..ff5d96f 100644
--- a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/RulesReader.java
+++ b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/RulesReader.java
@@ -30,6 +30,7 @@ import org.apache.skywalking.oap.server.core.alarm.provider.feishu.FeishuSetting
 import org.apache.skywalking.oap.server.core.alarm.provider.grpc.GRPCAlarmSetting;
 import org.apache.skywalking.oap.server.core.alarm.provider.slack.SlackSettings;
 import org.apache.skywalking.oap.server.core.alarm.provider.wechat.WechatSettings;
+import org.apache.skywalking.oap.server.core.alarm.provider.welink.WeLinkSettings;
 import org.yaml.snakeyaml.Yaml;
 import org.yaml.snakeyaml.constructor.SafeConstructor;
 
@@ -64,6 +65,7 @@ public class RulesReader {
             readCompositeRuleConfig(rules);
             readDingtalkConfig(rules);
             readFeishuConfig(rules);
+            readWeLinkConfig(rules);
         }
         return rules;
     }
@@ -251,4 +253,32 @@ public class RulesReader {
             rules.setFeishus(feishuSettings);
         }
     }
+
+    /**
+     * Read WeLink hook config into {@link WeLinkSettings}
+     */
+    private void readWeLinkConfig(Rules rules) {
+        Map welinkConfig = (Map) yamlData.get("welinkHooks");
+        if (welinkConfig != null) {
+            WeLinkSettings welinkSettings = new WeLinkSettings();
+            Object textTemplate = welinkConfig.getOrDefault("textTemplate", "");
+            welinkSettings.setTextTemplate((String) textTemplate);
+            List<Map<String, Object>> welinkWebHooks = (List<Map<String, Object>>) welinkConfig.get("webhooks");
+            if (welinkWebHooks != null) {
+                welinkWebHooks.forEach(welinkWebhook -> {
+                    String clientId = (String) welinkWebhook.getOrDefault("client_id", "");
+                    String clientSecret = (String) welinkWebhook.getOrDefault("client_secret", "");
+                    String accessTokenUrl = (String) welinkWebhook.getOrDefault("access_token_url", "");
+                    String messageUrl = (String) welinkWebhook.getOrDefault("message_url", "");
+                    String groupIds = (String) welinkWebhook.getOrDefault("group_ids", "");
+                    String rebootName = (String) welinkWebhook.getOrDefault("robot_name", "reboot");
+                    welinkSettings.getWebhooks()
+                                  .add(new WeLinkSettings.WebHookUrl(clientId, clientSecret, accessTokenUrl, messageUrl,
+                                                                     rebootName, groupIds
+                                  ));
+                });
+            }
+            rules.setWelinks(welinkSettings);
+        }
+    }
 }
diff --git a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/welink/WeLinkHookCallback.java b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/welink/WeLinkHookCallback.java
new file mode 100644
index 0000000..14140f6
--- /dev/null
+++ b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/welink/WeLinkHookCallback.java
@@ -0,0 +1,172 @@
+/*
+ * 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.skywalking.oap.server.core.alarm.provider.welink;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import io.netty.handler.codec.http.HttpHeaderValues;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Optional;
+import java.util.UUID;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.http.HttpHeaders;
+import org.apache.http.HttpStatus;
+import org.apache.http.StatusLine;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.util.EntityUtils;
+import org.apache.skywalking.oap.server.core.alarm.AlarmCallback;
+import org.apache.skywalking.oap.server.core.alarm.AlarmMessage;
+import org.apache.skywalking.oap.server.core.alarm.provider.AlarmRulesWatcher;
+
+/**
+ * Use SkyWalking alarm WeLink webhook API.
+ */
+@Slf4j
+public class WeLinkHookCallback implements AlarmCallback {
+
+    private static final int HTTP_CONNECT_TIMEOUT = 1000;
+    private static final int HTTP_CONNECTION_REQUEST_TIMEOUT = 1000;
+    private static final int HTTP_SOCKET_TIMEOUT = 10000;
+    private final AlarmRulesWatcher alarmRulesWatcher;
+    private final RequestConfig requestConfig;
+
+    public WeLinkHookCallback(final AlarmRulesWatcher alarmRulesWatcher) {
+        this.alarmRulesWatcher = alarmRulesWatcher;
+        this.requestConfig = RequestConfig.custom()
+                                          .setConnectTimeout(HTTP_CONNECT_TIMEOUT)
+                                          .setConnectionRequestTimeout(HTTP_CONNECTION_REQUEST_TIMEOUT)
+                                          .setSocketTimeout(HTTP_SOCKET_TIMEOUT)
+                                          .build();
+    }
+
+    /**
+     * Send alarm message if the settings not empty
+     */
+    @Override
+    public void doAlarm(List<AlarmMessage> alarmMessages) {
+        if (this.alarmRulesWatcher.getWeLinkSettings() == null || this.alarmRulesWatcher.getWeLinkSettings()
+                                                                                        .getWebhooks()
+                                                                                        .isEmpty()) {
+            return;
+        }
+        WeLinkSettings welinkSettings = this.alarmRulesWatcher.getWeLinkSettings();
+        welinkSettings.getWebhooks().forEach(webHookUrl -> {
+            String accessToken = getAccessToken(webHookUrl);
+            alarmMessages.forEach(alarmMessage -> {
+                String content = String.format(
+                    Locale.US,
+                    this.alarmRulesWatcher.getWeLinkSettings().getTextTemplate(),
+                    alarmMessage.getAlarmMessage()
+                );
+                sendAlarmMessage(webHookUrl, accessToken, content);
+            });
+        });
+    }
+
+    /**
+     * Send alarm message to remote endpoint
+     */
+    private void sendAlarmMessage(WeLinkSettings.WebHookUrl webHookUrl, String accessToken, String content) {
+        JsonObject appServiceInfo = new JsonObject();
+        appServiceInfo.addProperty("app_service_id", "1");
+        appServiceInfo.addProperty("app_service_name", webHookUrl.getRobotName());
+        JsonArray groupIds = new JsonArray();
+        Arrays.stream(webHookUrl.getGroupIds().split(",")).forEach(groupIds::add);
+        JsonObject body = new JsonObject();
+        body.add("app_service_info", appServiceInfo);
+        body.addProperty("app_msg_id", UUID.randomUUID().toString());
+        body.add("group_id", groupIds);
+        body.addProperty("content", String.format(
+            Locale.US, "<r><n></n><g>0</g><c>&lt;imbody&gt;&lt;imagelist/&gt;" +
+                "&lt;html&gt;&lt;![CDATA[&lt;DIV&gt;%s&lt;/DIV&gt;]]&gt;&lt;/html&gt;&lt;content&gt;&lt;![CDATA[%s]]&gt;&lt;/content&gt;&lt;/imbody&gt;</c></r>",
+            content, content
+        ));
+        body.addProperty("content_type", 0);
+        body.addProperty("client_app_id", "1");
+        sendPostRequest(
+            webHookUrl.getMessageUrl(), Collections.singletonMap("x-wlk-Authorization", accessToken), body.toString());
+    }
+
+    /**
+     * Get access token from remote endpoint
+     */
+    private String getAccessToken(WeLinkSettings.WebHookUrl webHookUrl) {
+        String accessTokenUrl = webHookUrl.getAccessTokenUrl();
+        String clientId = webHookUrl.getClientId();
+        String clientSecret = webHookUrl.getClientSecret();
+        String response = sendPostRequest(
+            accessTokenUrl, Collections.emptyMap(),
+            String.format(Locale.US, "{\"client_id\":%s,\"client_secret\":%s}", clientId, clientSecret)
+        );
+        Gson gson = new Gson();
+        JsonObject responseJson = gson.fromJson(response, JsonObject.class);
+        return Optional.ofNullable(responseJson)
+                       .map(r -> r.get("access_token"))
+                       .map(JsonElement::getAsString)
+                       .orElse("");
+    }
+
+    /**
+     * Post rest invoke
+     */
+    private String sendPostRequest(String url, Map<String, String> headers, String requestBody) {
+        String response = "";
+        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
+            HttpPost post = new HttpPost(url);
+            post.setConfig(requestConfig);
+            post.setHeader(HttpHeaders.ACCEPT, HttpHeaderValues.APPLICATION_JSON.toString());
+            post.setHeader(HttpHeaders.CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON.toString());
+            headers.forEach(post::setHeader);
+            StringEntity entity = new StringEntity(requestBody, ContentType.APPLICATION_JSON);
+            post.setEntity(entity);
+            try (CloseableHttpResponse httpResponse = httpClient.execute(post)) {
+                StatusLine statusLine = httpResponse.getStatusLine();
+                if (statusLine != null) {
+                    if (statusLine.getStatusCode() != HttpStatus.SC_OK) {
+                        log.error("send to {} failure. Response code: {}, Response content: {}", url,
+                                  statusLine.getStatusCode(),
+                                  EntityUtils.toString(httpResponse.getEntity())
+                        );
+                    } else {
+                        response = EntityUtils.toString(httpResponse.getEntity(), StandardCharsets.UTF_8);
+                    }
+                }
+            } catch (IOException e) {
+                log.error(e.getMessage(), e);
+            }
+        } catch (IOException e) {
+            log.error(e.getMessage(), e);
+        }
+        return response;
+    }
+}
diff --git a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/welink/WeLinkSettings.java b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/welink/WeLinkSettings.java
new file mode 100644
index 0000000..5861ba6
--- /dev/null
+++ b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/welink/WeLinkSettings.java
@@ -0,0 +1,60 @@
+/*
+ * 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.skywalking.oap.server.core.alarm.provider.welink;
+
+import java.util.ArrayList;
+import java.util.List;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.ToString;
+
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@Setter
+@Getter
+@ToString
+public class WeLinkSettings {
+
+    private String textTemplate;
+    @Builder.Default
+    private List<WebHookUrl> webhooks = new ArrayList<>();
+
+    @AllArgsConstructor
+    @Setter
+    @Getter
+    @ToString
+    public static class WebHookUrl {
+        // The unique identity of the application, used for interface authentication to obtain access_token
+        private final String clientId;
+        // The application key is used for interface authentication to obtain access_token
+        private final String clientSecret;
+        // The url get access token
+        private final String accessTokenUrl;
+        // The url to send message
+        private final String messageUrl;
+        // Name display in group
+        private final String robotName;
+        // The groupIds message to send
+        private final String groupIds;
+    }
+}
diff --git a/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmRulesWatcherTest.java b/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmRulesWatcherTest.java
index af5f29b..e10be74 100644
--- a/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmRulesWatcherTest.java
+++ b/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmRulesWatcherTest.java
@@ -90,6 +90,7 @@ public class AlarmRulesWatcherTest {
         assertNotNull(alarmRulesWatcher.getDingtalkSettings());
         assertNotNull(alarmRulesWatcher.getWechatSettings());
         assertNotNull(alarmRulesWatcher.getSlackSettings());
+        assertNotNull(alarmRulesWatcher.getWeLinkSettings());
     }
 
     @Test
diff --git a/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/welink/WeLinkHookCallbackTest.java b/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/welink/WeLinkHookCallbackTest.java
new file mode 100644
index 0000000..8a6e980
--- /dev/null
+++ b/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/welink/WeLinkHookCallbackTest.java
@@ -0,0 +1,154 @@
+/*
+ * 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.skywalking.oap.server.core.alarm.provider.welink;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.List;
+import javax.servlet.Servlet;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.skywalking.oap.server.core.alarm.AlarmMessage;
+import org.apache.skywalking.oap.server.core.alarm.provider.AlarmRulesWatcher;
+import org.apache.skywalking.oap.server.core.alarm.provider.Rules;
+import org.apache.skywalking.oap.server.core.source.DefaultScopeDefine;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertTrue;
+
+public class WeLinkHookCallbackTest implements Servlet {
+
+    private Server server;
+    private int port;
+    private volatile boolean isSuccess = false;
+    private int count;
+
+    @Before
+    public void init() throws Exception {
+        server = new Server(new InetSocketAddress("127.0.0.1", 0));
+        ServletContextHandler servletContextHandler = new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
+        servletContextHandler.setContextPath("/welinkhook");
+        server.setHandler(servletContextHandler);
+        ServletHolder servletHolder = new ServletHolder();
+        servletHolder.setServlet(this);
+        servletContextHandler.addServlet(servletHolder, "/api/auth/v2/tickets");
+        servletContextHandler.addServlet(servletHolder, "/api/welinkim/v1/im-service/chat/group-chat");
+        server.start();
+        port = server.getURI().getPort();
+        assertTrue(port > 0);
+    }
+
+    @Test
+    public void testWeLinkDoAlarm() {
+        List<WeLinkSettings.WebHookUrl> webHooks = new ArrayList<>();
+        webHooks.add(new WeLinkSettings.WebHookUrl("clientId", "clientSecret",
+                                                   "http://127.0.0.1:" + port + "/welinkhook/api/auth/v2/tickets",
+                                                   "http://127.0.0.1:" + port + "/welinkhook/api/welinkim/v1/im-service/chat/group-chat",
+                                                   "rebootName", "1,2,3"
+        ));
+        Rules rules = new Rules();
+        String template = "Apache SkyWalking Alarm: \n %s.";
+        rules.setWelinks(WeLinkSettings.builder().webhooks(webHooks).textTemplate(template).build());
+
+        AlarmRulesWatcher alarmRulesWatcher = new AlarmRulesWatcher(rules, null);
+        WeLinkHookCallback welinkHookCallback = new WeLinkHookCallback(alarmRulesWatcher);
+        List<AlarmMessage> alarmMessages = new ArrayList<>(2);
+        AlarmMessage alarmMessage = new AlarmMessage();
+        alarmMessage.setScopeId(DefaultScopeDefine.ALL);
+        alarmMessage.setRuleName("service_resp_time_rule");
+        alarmMessage.setAlarmMessage("alarmMessage with [DefaultScopeDefine.All]");
+        alarmMessages.add(alarmMessage);
+        AlarmMessage anotherAlarmMessage = new AlarmMessage();
+        anotherAlarmMessage.setRuleName("service_resp_time_rule_2");
+        anotherAlarmMessage.setScopeId(DefaultScopeDefine.ENDPOINT);
+        anotherAlarmMessage.setAlarmMessage("anotherAlarmMessage with [DefaultScopeDefine.Endpoint]");
+        alarmMessages.add(anotherAlarmMessage);
+        welinkHookCallback.doAlarm(alarmMessages);
+        Assert.assertTrue(isSuccess);
+    }
+
+    @After
+    public void stop() throws Exception {
+        server.stop();
+    }
+
+    @Override
+    public void init(ServletConfig servletConfig) {
+    }
+
+    @Override
+    public ServletConfig getServletConfig() {
+        return null;
+    }
+
+    @Override
+    public void service(ServletRequest request, ServletResponse response) throws IOException {
+        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
+        if (httpServletRequest.getContentType().equals("application/json")) {
+            InputStream inputStream = request.getInputStream();
+            ByteArrayOutputStream out = new ByteArrayOutputStream();
+            byte[] buffer = new byte[2048];
+            int readCntOnce;
+
+            while ((readCntOnce = inputStream.read(buffer)) >= 0) {
+                out.write(buffer, 0, readCntOnce);
+            }
+            JsonObject jsonObject = new Gson().fromJson(new String(out.toByteArray()), JsonObject.class);
+
+            if (count == 0) {
+                String clientId = jsonObject.get("client_id").getAsString();
+                count = clientId == null ? count : count + 1;
+                ((HttpServletResponse) response).setStatus(200);
+            } else if (count >= 1) {
+                String appMsgId = jsonObject.get("app_msg_id").getAsString();
+                count = appMsgId == null ? count : count + 1;
+                ((HttpServletResponse) response).setStatus(200);
+            } else {
+                ((HttpServletResponse) response).setStatus(500);
+            }
+            if (count == 2) {
+                isSuccess = true;
+            }
+        }
+    }
+
+    @Override
+    public String getServletInfo() {
+        return null;
+    }
+
+    @Override
+    public void destroy() {
+    }
+
+}
diff --git a/oap-server/server-alarm-plugin/src/test/resources/alarm-settings.yml b/oap-server/server-alarm-plugin/src/test/resources/alarm-settings.yml
index 0b31ddc..e337686 100755
--- a/oap-server/server-alarm-plugin/src/test/resources/alarm-settings.yml
+++ b/oap-server/server-alarm-plugin/src/test/resources/alarm-settings.yml
@@ -121,3 +121,16 @@ feishuHooks:
       secret: dummysecret
     - url: https://open.feishu.cn/open-apis/bot/v2/hook/dummy_token2
       secret:
+
+welinkHooks:
+  textTemplate: "Apache SkyWalking Alarm: \n %s."
+  webhooks:
+    # you may find your own client_id and client_secret in your app, below are dummy, need to change.
+    - client_id: "dummy_client_id"
+      client_secret: dummy_secret_key
+      access_token_url: https://open.welink.huaweicloud.com/api/auth/v2/tickets
+      message_url: https://open.welink.huaweicloud.com/api/welinkim/v1/im-service/chat/group-chat
+      # if you send to multi group at a time, separate group_ids with commas, e.g. "123xx","456xx"
+      group_ids: "dummy_group_id"
+      # make a name you like for the robot, it will display in group
+      robot_name: robot
\ No newline at end of file
diff --git a/oap-server/server-bootstrap/src/main/resources/alarm-settings.yml b/oap-server/server-bootstrap/src/main/resources/alarm-settings.yml
index 87ad2f5..0efbe26 100755
--- a/oap-server/server-bootstrap/src/main/resources/alarm-settings.yml
+++ b/oap-server/server-bootstrap/src/main/resources/alarm-settings.yml
@@ -97,3 +97,16 @@ feishuHooks:
   webhooks:
 #    - url: https://open.feishu.cn/open-apis/bot/v2/hook/dummy_token
 #      secret: dummysecret
+
+welinkHooks:
+  textTemplate: "Apache SkyWalking Alarm: \n %s."
+  webhooks:
+#    # you may find your own client_id and client_secret in your app, below are dummy, need to change.
+#    - client_id: "dummy_client_id"
+#      client_secret: dummy_secret_key
+#      access_token_url: https://open.welink.huaweicloud.com/api/auth/v2/tickets
+#      message_url: https://open.welink.huaweicloud.com/api/welinkim/v1/im-service/chat/group-chat
+#      # if you send to multi group at a time, separate group_ids with commas, e.g. "123xx","456xx"
+#      group_ids: "dummy_group_id"
+#      # make a name you like for the robot, it will display in group
+#      robot_name: robot