Skip to content

Commit 11952f8

Browse files
committed
feat(backend): 接入海磊回收和xwork检查 #10271
# Reviewed, transaction id: 41398
1 parent 6eb9381 commit 11952f8

File tree

14 files changed

+170
-18
lines changed

14 files changed

+170
-18
lines changed

dbm-ui/backend/components/domains.py

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
NAMESERVICE_APIGW_DOMAIN = env.NAMESERVICE_APIGW_DOMAIN or ESB_DOMAIN_TPL.format("nameservice")
4343
HADB_APIGW_DOMAIN = env.HADB_APIGW_DOMAIN or ESB_DOMAIN_TPL.format("hadb")
4444
HCM_APIGW_DOMAIN = env.HCM_APIGW_DOMAIN
45+
XWORK_APIGW_DOMAIN = env.XWORK_APIGW_DOMAIN
4546
UWORK_APIGW_DOMAIN = env.UWORK_APIGW_DOMAIN or ESB_DOMAIN_TPL.format("uwork")
4647
DBRESOURCE_APIGW_DOMAIN = env.DBRESOURCE_APIGW_DOMAIN or ESB_DOMAIN_TPL.format("dbresource")
4748
BACKUP_APIGW_DOMAIN = env.BACKUP_APIGW_DOMAIN or ESB_DOMAIN_TPL.format("backup")

dbm-ui/backend/components/hcm/client.py

+18-1
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@
99
See the License for the specific language governing permissions and limitations under the License.
1010
"""
1111

12-
from django.utils.translation import ugettext_lazy as _
12+
from django.utils.translation import gettext_lazy as _
1313

14+
from ... import env
1415
from .. import CCApi
1516
from ..base import BaseApi
1617
from ..domains import HCM_APIGW_DOMAIN
@@ -36,6 +37,11 @@ def __init__(self):
3637
url="/api/v1/woa/bizs/{bk_biz_id}/task/hosts/uwork_tickets/status/check/",
3738
description=_("检查主机是否有未完结的uwork单据"),
3839
)
40+
self.create_biz_recycle = self.generate_data_api(
41+
method="POST",
42+
url="/api/v1/woa/bizs/{bk_biz_id}/task/create/recycle/order",
43+
description=_("创建业务下的资源回收单据"),
44+
)
3945

4046
def check_host_is_dissolved(self, bk_host_ids: list):
4147
if not HCM_APIGW_DOMAIN:
@@ -59,5 +65,16 @@ def check_host_has_uwork(self, bk_host_ids: list):
5965
has_uwork_hosts_map = {d["bk_host_id"]: d for d in resp["details"] if d["has_open_tickets"]}
6066
return has_uwork_hosts_map
6167

68+
def create_recycle(self, bk_host_ids: list):
69+
params = {
70+
"bk_biz_id": env.DBA_APP_BK_BIZ_ID,
71+
"bk_host_ids": bk_host_ids,
72+
"remark": "dbm auto create",
73+
# 回收策略固定是:立刻销毁
74+
"return_plan": {"cvm": "IMMEDIATE", "pm": "IMMEDIATE"},
75+
}
76+
resp = self.create_biz_recycle(params=params)
77+
return resp["info"][0]["order_id"]
78+
6279

6380
HCMApi = _HCMApi()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-DB管理系统(BlueKing-BK-DBM) available.
4+
Copyright (C) 2017-2023 THL A29 Limited, a Tencent company. All rights reserved.
5+
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at https://opensource.org/licenses/MIT
7+
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
8+
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
9+
specific language governing permissions and limitations under the License.
10+
"""
+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-DB管理系统(BlueKing-BK-DBM) available.
4+
Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved.
5+
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at https://opensource.org/licenses/MIT
7+
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
8+
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9+
See the License for the specific language governing permissions and limitations under the License.
10+
"""
11+
import hashlib
12+
import hmac
13+
import time
14+
from datetime import timedelta
15+
from typing import Dict
16+
17+
from django.utils.translation import ugettext_lazy as _
18+
19+
from backend import env
20+
from backend.components.base import BaseApi
21+
from backend.components.domains import XWORK_APIGW_DOMAIN
22+
from backend.utils.time import timestamp2datetime
23+
24+
25+
class _XworkApi(BaseApi):
26+
MODULE = _("Xwork 服务")
27+
BASE = XWORK_APIGW_DOMAIN
28+
29+
# 待授权/已预约/处理中
30+
XWORK_UNFINISHED_CODE = [4, 5, 6]
31+
# 已结束/已避免/已取消
32+
XWORK_FINISHED_CODE = [7, 8, 9]
33+
34+
def __init__(self):
35+
self.xwork_list = self.generate_data_api(
36+
method="POST",
37+
url="/fault_task/search",
38+
description=_("【查询】故障告警数据接口"),
39+
)
40+
41+
@staticmethod
42+
def __generate_signature():
43+
"""生成请求签名"""
44+
now = int(time.time())
45+
caller_key = bytes(env.XWORK_CALLER_KEY, encoding="utf8")
46+
hd_str = (str(now)).encode("utf-8")
47+
signature = hmac.new(caller_key, hd_str, digestmod=hashlib.sha512).hexdigest()
48+
return now, signature
49+
50+
def check_xwork_list(self, host_ip_map: Dict[str, int]):
51+
"""
52+
检查主机是否有xwork单据
53+
@param host_ip_map: 主机ip和主机ID的映射
54+
"""
55+
if not XWORK_APIGW_DOMAIN:
56+
return {}
57+
58+
# 查询xwork单据,默认查询一年以内
59+
now, signature = self.__generate_signature()
60+
end_time = timestamp2datetime(now)
61+
start_time = end_time - timedelta(days=365)
62+
params = {
63+
"CallerName": env.XWORK_CALLER_USER,
64+
"Authorization": {"TimeStamp": now, "Signature": signature},
65+
"ServiceParam": {
66+
"Filters": {
67+
"BsiIp": list(host_ip_map.keys()),
68+
"TaskStatusList": self.XWORK_UNFINISHED_CODE,
69+
"SearchStartTime": start_time.strftime("%Y-%m-%d %H:%M:%S"),
70+
"SearchEndTime": end_time.strftime("%Y-%m-%d %H:%M:%S"),
71+
}
72+
},
73+
"Order": {},
74+
"Offset": 0,
75+
"Limit": len(host_ip_map),
76+
}
77+
data = self.xwork_list(params, raw=True)["ServiceResult"]["Data"]
78+
# 映射xwork与host
79+
has_xwork_hosts_map = {host_ip_map[d["BsiIp"]]: d for d in data}
80+
return has_xwork_hosts_map
81+
82+
83+
XworkApi = _XworkApi()

dbm-ui/backend/configuration/views/system.py

+1
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ def environ(self, request):
122122
"BK_SCR_URL": env.BK_SCR_URL,
123123
"BKDATA_FRONTEND_REPORT_URL": BKBaseApi.get_bkdata_frontend_report_url(),
124124
"BKMONITOR_URL": env.BKMONITOR_URL,
125+
"BK_HCM_URL": env.BK_HCM_URL,
125126
}
126127
)
127128
return Response(envs)

dbm-ui/backend/db_dirty/filters.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,12 @@ class DirtyMachinePoolFilter(filters.FilterSet):
5252
rack_id = filters.CharFilter(field_name="rack_id", lookup_expr="icontains", label=_("机架"))
5353
device_class = filters.CharFilter(field_name="device_class", lookup_expr="icontains", label=_("机型"))
5454
os_name = filters.CharFilter(field_name="os_name", lookup_expr="icontains", label=_("操作系统"))
55+
update_at__lte = filters.DateTimeFilter(field_name="update_at", lookup_expr="lte", label=_("更新时间早于"))
56+
update_at__gte = filters.DateTimeFilter(field_name="update_at", lookup_expr="gte", label=_("更新时间晚于"))
5557

5658
def filter_ips(self, queryset, name, value):
5759
return queryset.filter(ip__in=value.split(","))
5860

5961
class Meta:
6062
model = DirtyMachine
61-
fields = {"creator": ["exact"], "pool": ["exact"]}
63+
fields = {"updater": ["exact"], "pool": ["exact"]}

dbm-ui/backend/db_dirty/handlers.py

+23-4
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@
1717

1818
from backend import env
1919
from backend.components.dbresource.client import DBResourceApi
20+
from backend.components.hcm.client import HCMApi
2021
from backend.db_dirty.constants import MachineEventType, PoolType
2122
from backend.db_dirty.exceptions import PoolTransferException
2223
from backend.db_dirty.models import DirtyMachine, MachineEvent
2324
from backend.db_meta.models import Machine
25+
from backend.env import HCM_APIGW_DOMAIN
2426
from backend.flow.utils.cc_manage import CcManage
2527

2628
logger = logging.getLogger("root")
@@ -33,7 +35,13 @@ class DBDirtyMachineHandler(object):
3335

3436
@classmethod
3537
def transfer_hosts_to_pool(
36-
cls, operator: str, bk_host_ids: List[int], source: PoolType, target: PoolType, remark: str = ""
38+
cls,
39+
operator: str,
40+
bk_host_ids: List[int],
41+
source: PoolType,
42+
target: PoolType,
43+
remark: str = "",
44+
hcm_recycle: bool = False,
3745
):
3846
"""
3947
将主机转移待回收/故障池模块
@@ -42,21 +50,32 @@ def transfer_hosts_to_pool(
4250
@param source: 主机来源
4351
@param target: 主机去向
4452
@param remark: 备注
53+
@param hcm_recycle: 是否在hcm创建回收单据,仅针对主机回收场景
4554
"""
46-
# 将主机按照业务分组
55+
bk_biz_id = env.DBA_APP_BK_BIZ_ID
4756
recycle_hosts = DirtyMachine.objects.filter(bk_host_id__in=bk_host_ids)
4857
hosts = [{"bk_host_id": host.bk_host_id} for host in recycle_hosts]
49-
bk_biz_id = env.DBA_APP_BK_BIZ_ID
58+
recycle_id = None
59+
5060
# 待回收 ---> 回收
5161
if source == PoolType.Recycle and target == PoolType.Recycled:
52-
MachineEvent.host_event_trigger(bk_biz_id, hosts, MachineEventType.Recycled, operator, remark=remark)
62+
message = _("主机删除成功!")
5363
CcManage(bk_biz_id, "").recycle_host(bk_host_ids)
64+
# 如果配置了hcm,并且确认在hcm回收,则自动创建回收单据
65+
if HCM_APIGW_DOMAIN and hcm_recycle:
66+
recycle_id = HCMApi.create_recycle(bk_host_ids)
67+
remark = _("已自动在「海垒」创建回收单据(单号:{})").format(recycle_id)
68+
message += remark
69+
MachineEvent.host_event_trigger(bk_biz_id, hosts, MachineEventType.Recycled, operator, remark=remark)
5470
# 故障池 ---> 待回收
5571
elif source == PoolType.Fault and target == PoolType.Recycle:
72+
message = _("主机转移成功!")
5673
MachineEvent.host_event_trigger(bk_biz_id, hosts, MachineEventType.ToRecycle, operator, remark=remark)
5774
else:
5875
raise PoolTransferException(_("{}--->{}转移不合法").format(source, target))
5976

77+
return {"message": message, "hcm_recycle_id": recycle_id}
78+
6079
@classmethod
6180
def migrate_machine_to_host_pool(cls):
6281
"""

dbm-ui/backend/db_dirty/serializers.py

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ class TransferDirtyMachineSerializer(serializers.Serializer):
2424
source = serializers.ChoiceField(help_text=_("主机来源"), choices=PoolType.get_choices())
2525
target = serializers.ChoiceField(help_text=_("主机去向"), choices=PoolType.get_choices())
2626
remark = serializers.CharField(help_text=_("备注"), required=False)
27+
hcm_recycle = serializers.BooleanField(help_text=_("是否从海磊自动回收"), required=False, default=False)
2728

2829

2930
class ListMachineEventSerializer(serializers.ModelSerializer):

dbm-ui/backend/db_dirty/views.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,7 @@ class DBDirtyMachineViewSet(viewsets.SystemViewSet):
5555
@action(detail=False, methods=["POST"], serializer_class=TransferDirtyMachineSerializer)
5656
def transfer_hosts_to_pool(self, request):
5757
data = self.params_validate(self.get_serializer_class())
58-
DBDirtyMachineHandler.transfer_hosts_to_pool(operator=request.user.username, **data)
59-
return Response()
58+
return Response(DBDirtyMachineHandler.transfer_hosts_to_pool(operator=request.user.username, **data))
6059

6160
@common_swagger_auto_schema(
6261
operation_summary=_("机器事件列表"),

dbm-ui/backend/db_monitor/serializers.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,6 @@ class Meta:
327327

328328
class CreateAlarmShieldSerializer(serializers.Serializer):
329329
category = serializers.CharField(help_text=_("屏蔽类型"))
330-
bk_biz_id = serializers.IntegerField(help_text=_("业务ID"))
331330
dimension_config = serializers.DictField(help_text=_("屏蔽维度配置"))
332331
shield_notice = serializers.BaseSerializer(help_text=_("告警屏蔽通知"), default=False)
333332
begin_time = serializers.CharField(help_text=_("开始时间"))
@@ -343,8 +342,9 @@ def validate(self, attrs):
343342
for condition in attrs["dimension_config"]["dimension_conditions"]:
344343
if "appid" in condition["key"]:
345344
appid = condition["value"][0]
346-
if int(attrs["bk_biz_id"]) != int(appid):
347-
raise serializers.ValidationError(_("维度配置的业务ID与屏蔽策略业务ID不同"))
345+
if not appid:
346+
raise serializers.ValidationError(_("暂不支持屏蔽[不包含业务]维度的告警"))
347+
attrs["bk_biz_id"] = appid
348348
return attrs
349349

350350
class Meta:

dbm-ui/backend/db_services/dbresource/serializers.py

+13-6
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
from backend import env
1717
from backend.components.hcm.client import HCMApi
18+
from backend.components.xwork.client import XworkApi
1819
from backend.configuration.constants import DBType
1920
from backend.constants import INT_MAX
2021
from backend.db_dirty.constants import MachineEventType
@@ -58,18 +59,24 @@ def validate(self, attrs):
5859
# 如果主机存在元数据,则拒绝导入
5960
exist_hosts = list(Machine.objects.filter(bk_host_id__in=host_ids).values_list("ip", flat=True))
6061
if exist_hosts:
61-
raise serializers.ValidationError(_("导入主机{}存在元数据,请检查后重新导入").format(exist_hosts))
62+
raise serializers.ValidationError(_("导入失败,主机{}存在元数据,请检查后重新导入").format(exist_hosts))
6263

6364
# 存在uwork或者是待裁撤主机,则不允许导入
64-
check_work = HCMApi.check_host_has_uwork(host_ids)
65-
if check_work:
66-
ips = [host_id__ip_map[host_id] for host_id in check_work.keys()]
67-
raise serializers.ValidationError(_("导入主机{}存在uwork单据,请处理后重新导入").format(ips))
65+
check_uwork = HCMApi.check_host_has_uwork(host_ids)
66+
if check_uwork:
67+
ips = [host_id__ip_map[host_id] for host_id in check_uwork.keys()]
68+
raise serializers.ValidationError(_("导入失败,检测主机{}有关联的uwork单据,请检查后重新导入").format(ips))
69+
70+
host_ip__host_id_map = {host["ip"]: host["host_id"] for host in attrs["hosts"]}
71+
check_xwork = XworkApi.check_xwork_list(host_ip__host_id_map)
72+
if check_xwork:
73+
ips = [host_id__ip_map[host_id] for host_id in check_xwork.keys()]
74+
raise serializers.ValidationError(_("导入失败,检测主机{}有关联的xwork单据,请检查后重新导入").format(ips))
6875

6976
check_dissolved = HCMApi.check_host_is_dissolved(host_ids)
7077
if check_dissolved:
7178
ips = [host_id__ip_map[host_id] for host_id in check_dissolved]
72-
raise serializers.ValidationError(_("导入主机包含裁撤主机:{},无法进行导入").format(ips))
79+
raise serializers.ValidationError(_("导入失败,检测主机{}为待裁撤主机,请检查后重新导入").format(ips))
7380

7481
return attrs
7582

dbm-ui/backend/env/__init__.py

+5
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@
103103
BK_JOB_URL = get_type_env(key="BK_JOB_HOST", _type=str, default=None)
104104
BK_NODEMAN_URL = get_type_env(key="BK_NODEMAN_URL", _type=str, default="http://apps.example.com/bk--nodeman")
105105
BK_SCR_URL = get_type_env(key="BK_SCR_URL", _type=str, default="http://scr.example.com")
106+
BK_HCM_URL = get_type_env(key="BK_HCM_URL", _type=str, default="")
106107
BK_SOPS_URL = get_type_env(key="BK_SOPS_HOST", _type=str, default=None)
107108
BK_HELPER_URL = get_type_env(key="BK_HELPER_URL", _type=str, default=None)
108109
# 北极星服务
@@ -178,6 +179,10 @@
178179
# gcs/scr平台
179180
GCS_SCR_OPERATOR = get_type_env(key="GCS_SCR_OPERATOR", _type=str, default="scr-system")
180181

182+
# HCM/Uwork/Xwork
183+
XWORK_CALLER_USER = get_type_env(key="XWORK_CALLER_USER", _type=str, default="")
184+
XWORK_CALLER_KEY = get_type_env(key="XWORK_CALLER_KEY", _type=str, default="")
185+
181186
# 是否启动mysql-dbbackup程序的版本逻辑选择,不启动默认统一安装社区版本
182187
MYSQL_BACKUP_PKG_MAP_ENABLE = get_type_env(key="MYSQL_BACKUP_PKG_MAP_ENABLE", _type=bool, default=False)
183188

dbm-ui/backend/env/apigw_domains.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@
3030
DRS_APIGW_DOMAIN = get_type_env(key="DRS_APIGW_DOMAIN", _type=str)
3131
NAMESERVICE_APIGW_DOMAIN = get_type_env(key="NAMESERVICE_APIGW_DOMAIN", _type=str)
3232
HCM_APIGW_DOMAIN = get_type_env(key="HCM_APIGW_DOMAIN", _type=str, default="")
33-
UWORK_APIGW_DOMAIN = get_type_env(key="UWORK_APIGW_DOMAIN", _type=str)
33+
UWORK_APIGW_DOMAIN = get_type_env(key="UWORK_APIGW_DOMAIN", _type=str, default="")
34+
XWORK_APIGW_DOMAIN = get_type_env(key="XWORK_APIGW_DOMAIN", _type=str, default="")
3435

3536
DBCONFIG_APIGW_DOMAIN = get_type_env(key="DBCONFIG_APIGW_DOMAIN", _type=str, default="http://bk-dbm-dbconfig")
3637
DNS_APIGW_DOMAIN = get_type_env(key="DNS_APIGW_DOMAIN", _type=str, default="http://bk-dbm-db-dns-api")

dbm-ui/backend/ticket/models/ticket.py

+6
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from backend.bk_web.constants import LEN_L_LONG, LEN_LONG, LEN_NORMAL, LEN_SHORT
2222
from backend.bk_web.models import AuditedModel
2323
from backend.components.hcm.client import HCMApi
24+
from backend.components.xwork.client import XworkApi
2425
from backend.configuration.constants import PLAT_BIZ_ID, DBType, SystemSettingsEnum
2526
from backend.configuration.models import BizSettings, DBAdministrator, SystemSettings
2627
from backend.db_monitor.exceptions import AutofixException
@@ -287,10 +288,15 @@ def add_host_remark(add_hosts, remark):
287288
# 存在uwork的主机需要回到故障池,存在裁撤单的主机需要回到待回收池,否则退回资源池
288289
dissolved_hosts = HCMApi.check_host_is_dissolved(host_ids)
289290
uwork_hosts = HCMApi.check_host_has_uwork(host_ids)
291+
host_ip__host_id_map = {host["ip"]: host["bk_host_id"] for host in hosts}
292+
xwork_hosts = XworkApi.check_xwork_list(host_ip__host_id_map)
290293
for host in hosts:
291294
if host["bk_host_id"] in uwork_hosts.keys():
292295
host.update(remark=_("检测主机有关联的uwork单据"))
293296
fault_hosts.append(host)
297+
elif host["bk_host_id"] in xwork_hosts.keys():
298+
host.update(remark=_("检测主机有关联的xwork单据"))
299+
fault_hosts.append(host)
294300
elif host["bk_host_id"] in dissolved_hosts:
295301
host.update(remark=_("检测主机为待裁撤主机"))
296302
recycle_hosts.append(host)

0 commit comments

Comments
 (0)