Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dbm-ui/backend/core/notify/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@
specific language governing permissions and limitations under the License.
"""

from .handlers import NotifyAdapter, send_msg
from .handlers import NotifyAdapter, send_msg, send_msg_for_ai_task_guardian
3 changes: 3 additions & 0 deletions dbm-ui/backend/core/notify/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,6 @@ class MsgType(StrStructuredEnum):

# 默认通知:企业微信
DEFAULT_BIZ_NOTIFY_CONFIG = {status: {MsgType.RTX.value: True} for status in TicketStatus.get_values()}

# 单据值守默认通知方式: 企业微信
DEFAULT_BIZ_AI_NOTIFY_CONFIG = {"AI_TASK_GUARDIAN": {MsgType.RTX.value: True}}
66 changes: 64 additions & 2 deletions dbm-ui/backend/core/notify/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,15 @@
from backend.components.bkchat.client import BkChatApi
from backend.configuration.constants import BizSettingsEnum
from backend.configuration.models import BizSettings
from backend.core.notify.constants import DEFAULT_BIZ_NOTIFY_CONFIG, MsgType
from backend.core.notify.constants import DEFAULT_BIZ_AI_NOTIFY_CONFIG, DEFAULT_BIZ_NOTIFY_CONFIG, MsgType
from backend.core.notify.exceptions import NotifyBaseException
from backend.core.notify.template import FAILED_TEMPLATE, FINISHED_TEMPLATE, TERMINATE_TEMPLATE, TODO_TEMPLATE
from backend.core.notify.template import (
AI_TASK_GUARDIAN_TEMPLATE,
FAILED_TEMPLATE,
FINISHED_TEMPLATE,
TERMINATE_TEMPLATE,
TODO_TEMPLATE,
)
from backend.db_meta.models import AppCache
from backend.env import DEFAULT_USERNAME
from backend.exceptions import ApiResultError
Expand Down Expand Up @@ -344,6 +350,29 @@ def render_msg_template(self, msg_type: str):
content = textwrap.dedent(template.render(payload))
return title, content

def render_msg_template_for_ai_task(self, ai_result: str):
"""
拼装单据值守推送的信息内容,以及标题
"""
biz = AppCache.objects.get(bk_biz_id=self.bk_biz_id)
title = _("「DBM」:检测到您的{ticket_type}单据「{ticket_id}」有风险").format(
ticket_type=TicketType.get_choice_label(self.ticket.ticket_type),
ticket_id=self.ticket.id,
)
# 渲染通知内容
jinja_env = Environment()
template = jinja_env.from_string(AI_TASK_GUARDIAN_TEMPLATE)
payload = {
"creator": self.ticket.creator,
"biz_name": f"{biz.bk_biz_name}(#{self.bk_biz_id}, {biz.db_app_abbr})",
"cluster_domains": ",".join(self.clusters),
"submit_time": self.ticket.create_at.astimezone().strftime("%Y-%m-%d %H:%M:%S%z"),
"running_time": f"{self.ticket.get_cost_time()}s",
"ai_result": ai_result,
}
content = textwrap.dedent(template.render(payload))
return title, content

def send_msg(self):
# 获取单据通知设置,优先: 单据配置 > 业务配置 > 默认业务配置
if self.phase in self.ticket.msg_config:
Expand Down Expand Up @@ -378,8 +407,41 @@ def send_msg(self):
except (ApiResultError, Exception) as e:
logger.error(_("[{}]消息发送失败,错误信息: {}").format(MsgType.get_choice_label(msg_type), e))

def send_msg_of_ai_task_guardian(self, ai_result: str):
"""
定义AI单据值守推送消息的逻辑
@param ai_result: 调用智能体之后的结果信息
"""
biz_notify_config = BizSettings.get_setting_value(
self.bk_biz_id, key=BizSettingsEnum.NOTIFY_CONFIG, default=DEFAULT_BIZ_AI_NOTIFY_CONFIG
)
send_msg_config = biz_notify_config["AI_TASK_GUARDIAN"]

send_msg_types = [msg_type for msg_type in send_msg_config if send_msg_config.get(msg_type)]
for msg_type in send_msg_types:
notify_class, context = self.get_notify_class(msg_type)

# 如果是群机器人通知,则接受者为群ID
if msg_type == MsgType.WECOM_ROBOT:
self.receivers = send_msg_config.get(MsgType.WECOM_ROBOT.value, [])

# 拼接信息通知
title, content = self.render_msg_template_for_ai_task(ai_result=ai_result)

try:
notify_class(title, content, self.receivers).send_msg(msg_type, context=context)
except (ApiResultError, Exception) as e:
logger.error(_("[{}]消息发送失败,错误信息: {}").format(MsgType.get_choice_label(msg_type), e))


@shared_task
def send_msg(ticket_id: int, deadline: int = None):
# 可异步发送消息,非阻塞路径默认不抛出异常
NotifyAdapter(ticket_id, deadline).send_msg()


@shared_task
def send_msg_for_ai_task_guardian(ticket_id: int, ai_result: str, deadline: int = None):
# 可异步发送消息,非阻塞路径默认不抛出异常
# AI单据值守消息通道专属
NotifyAdapter(ticket_id, deadline).send_msg_of_ai_task_guardian(ai_result=ai_result)
12 changes: 12 additions & 0 deletions dbm-ui/backend/core/notify/template.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,15 @@
查看详情: {{detail_address}}\
"""
)

# 定义单据值守的通知模板
AI_TASK_GUARDIAN_TEMPLATE = _(
"""\
申请人: {{creator}}
业务: {{biz_name}}
相关域名: {{cluster_domains}}
申请时间: {{submit_time}}
执行时间: {{running_time}}
AI检测详情: {{ai_result}}\
"""
)
2 changes: 1 addition & 1 deletion dbm-ui/backend/db_monitor/views/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ def search(self, request):
conditions.append(f'({" OR ".join(cluster_type_conditions)})')

params["query_string"] = " AND ".join(conditions)

print(params)
data = BKMonitorV3Api.search_alert(params)
# 对于维度不包含appid的,暂时标记,无法做到鉴权和屏蔽
for alert in data["alerts"]:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
"""
TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-DB管理系统(BlueKing-BK-DBM) available.
Copyright (C) 2017-2023 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at https://opensource.org/licenses/MIT
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.
"""
from datetime import datetime
from typing import Dict, List

from django.utils.translation import gettext as _

from backend import env
from backend.components import BKMonitorV3Api


class QueryMonitorAlarm(object):
"""
处理查询监控数据的类
"""

@staticmethod
def filter_alarm_by_create_time(alerts: List[Dict], start_time: datetime, end_time: datetime):
"""
感觉蓝鲸监控返回的告警条数,在做二次过滤,过滤出某个时间范围中,生成的告警记录
因为search_alert返回告警条目的时候,只要某类告警记录,它的未恢复的时间区间,和你传入的时间区间有交集,则也会返回过来
但往往我们可能需要这个时间区间所产生的告警记录,所以设计这个方法,根据每条记录的 create_time, 做二次过滤
@param alerts: 蓝鲸监控返回的告警记录列表
@param start_time: 起始时间点
@param end_time: 截止时间点
"""
filter_alerts = []
for alert in alerts:
if int(start_time.timestamp()) <= alert["create_time"] <= int(end_time.timestamp()):
filter_alerts.append(alert)
return filter_alerts

@staticmethod
def query_alarm_for_cluster_ids(
bk_biz_id: int, cluster_domains: List[str], start_time: datetime, end_time: datetime
):
"""
根据传入的时间范围,查询这段时间内的这批集群ID的告警信息
@param cluster_domains: 查询的集群域名列表
@param start_time: 查询的起始时间点
@param end_time: 查询的截止时间点
"""
query_param = {
"bk_biz_ids": [env.DBA_APP_BK_BIZ_ID],
"start_time": int(start_time.timestamp()),
"end_time": int(end_time.timestamp()),
"page": 1,
"page_size": 2,
"status": ["ABNORMAL"],
"show_aggs": False,
"show_overview": False,
"query_string": "",
}
# 通用查询条件拼接 querystring
conditions = [
f'tags.appid:"{bk_biz_id}"',
" OR ".join([f'tags.cluster_domain:"{c}"' for c in cluster_domains]),
]
query_param["query_string"] = " AND ".join(conditions)

# 获取所有告警记录
all_alerts = []
while True:
try:
data = BKMonitorV3Api.search_alert(query_param)
all_alerts.extend(data["alerts"])
if len(data["alerts"]) == int(data["total"]):
# 代表返回的实际条数,与匹配到总条数相等,则证明这次的返回,已经全部返回,无需进入下一轮请求
break
if len(data["alerts"]) == 0:
# 代表这次分页调用,已经到最后一步,无需进入下一轮请求
break

# 修改query_param的分页参数,进入下一轮请求
query_param["page"] += 1

except Exception as err:
raise Exception(_("调用BKMonitorV3Api失败:{}".format(err)))

# 查询出来的结果
return [
{
"alarm_id": alart["id"],
"alert_name": alart["alert_name"],
"alert_status": alart["status"],
"alert_create_time": int(alart["create_time"]),
"tags": alart["tags"],
}
for alart in QueryMonitorAlarm.filter_alarm_by_create_time(all_alerts, start_time, end_time)
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""
TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-DB管理系统(BlueKing-BK-DBM) available.
Copyright (C) 2017-2023 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at https://opensource.org/licenses/MIT
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.
"""

from django.utils.translation import gettext_lazy as _
from rest_framework import serializers


class AlertTagsSerializer(serializers.Serializer):
key = serializers.CharField(help_text=_("标签名称"))
value = serializers.CharField(help_text=_("标签值"))


class AlertInfoSerializer(serializers.Serializer):
alarm_id = serializers.CharField(help_text=_("告警记录Id"))
alert_name = serializers.CharField(help_text=_("告警记录名称"))
alert_status = serializers.CharField(help_text=_("告警记录状态"))
alert_create_time = serializers.IntegerField(help_text=_("告警记录创建时间戳"))
tags = serializers.ListField(child=AlertTagsSerializer(), help_text=_("告警记录的标签信息"))


class SearchAlertInputSerializer(serializers.Serializer):
bk_biz_id = serializers.IntegerField(help_text=_("业务Id"))
cluster_domains = serializers.ListField(child=serializers.CharField(), help_text=_("待查询的集群域名列表"))
start_time = serializers.DateTimeField(help_text=_("查询的起始时间"))
end_time = serializers.DateTimeField(help_text=_("查询的截止时间"))


class SearchAlertOutputSerializer(serializers.Serializer):
alert_infos = serializers.ListField(child=AlertInfoSerializer(), help_text=_("告警记录信息"))
2 changes: 2 additions & 0 deletions dbm-ui/backend/dbm_aiagent/mcp_tools/common/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@
DBMetaQueryMcpToolsViewSet,
ResourceParamQueryMcpToolsViewSet,
)
from backend.dbm_aiagent.mcp_tools.common.views.alram_query import MonitorQueryMcpToolsViewSet

routers = DefaultRouter(trailing_slash=True)

routers.register(r"", DBMetaQueryMcpToolsViewSet, basename="mcp-dbmeta-query")
routers.register(r"", BillQueryMcpToolsViewSet, basename="mcp-bill-query")
routers.register(r"", ResourceParamQueryMcpToolsViewSet, basename="mcp-resource-query")
routers.register(r"", MonitorQueryMcpToolsViewSet, basename="mcp-monitor-query")
urlpatterns = routers.urls
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""
TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-DB管理系统(BlueKing-BK-DBM) available.
Copyright (C) 2017-2023 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at https://opensource.org/licenses/MIT
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.
"""
from django.utils.translation import gettext_lazy as _
from rest_framework.response import Response

from backend.dbm_aiagent.mcp_tools.common.impl.query_moitor_alarm_info import QueryMonitorAlarm
from backend.dbm_aiagent.mcp_tools.common.serializers.alarm_query import (
SearchAlertInputSerializer,
SearchAlertOutputSerializer,
)
from backend.dbm_aiagent.mcp_tools.constants import DBMAMcpTools, DBMMCPTags
from backend.dbm_aiagent.mcp_tools.decorators import mcp_tools_api_decorator
from backend.dbm_aiagent.mcp_tools.views import McpToolsViewSet
from backend.iam_app.handlers.drf_perm.base import RejectPermission


class MonitorQueryMcpToolsViewSet(McpToolsViewSet):
default_permission_class = [RejectPermission()]

@mcp_tools_api_decorator(
description=str(_("查询某个时间区间, 某批集群ID对应的产生的告警记录")),
request_slz=SearchAlertInputSerializer,
response_slz=SearchAlertOutputSerializer,
tags=[DBMMCPTags.READ],
mcp=[DBMAMcpTools.ALARM_QUERY],
name_prefix="alarm_query",
)
def query_monitor_alarm_info(self, request, *args, **kwargs):
bk_biz_id = int(self.get_param("bk_biz_id"))
cluster_domains = self.get_param("cluster_domains")
start_time = self.get_param("start_time")
end_time = self.get_param("end_time")
return Response(
QueryMonitorAlarm.query_alarm_for_cluster_ids(
bk_biz_id=bk_biz_id, cluster_domains=cluster_domains, start_time=start_time, end_time=end_time
)
)
1 change: 1 addition & 0 deletions dbm-ui/backend/dbm_aiagent/mcp_tools/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class DBMAMcpTools(StrStructuredEnum):
MYSQL_SLOWLOG = EnumField("mysql-slowlog", "mysql-slowlog")
SQLSERVER_QUERY = EnumField("sqlserver-query", "sqlserver-query")
BILL_QUERY = EnumField("bill-query", "bill-query")
ALARM_QUERY = EnumField("alarm-query", "alarm-query")
SQL_SYNTAX_CHECK = EnumField("sql-syntax-check", "sql-syntax-check")
RESOURCE_QUERY = EnumField("resource-query", "resource-query")
REDIS_QUERY_META = EnumField("redis-query-meta", "redis-query-meta")
Expand Down
Empty file.
Loading
Loading