Skip to content

Commit 5265d1d

Browse files
committed
feat(backend): 密码修改支持异步模式 #7598
1 parent 31c7853 commit 5265d1d

File tree

9 files changed

+89
-14
lines changed

9 files changed

+89
-14
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ def __init__(self):
128128
method="POST",
129129
url="/priv/modify_admin_password",
130130
description=_("新增或者修改实例中管理用户的密码"),
131+
default_timeout=self.TIMEOUT,
131132
)
132133
self.get_password = self.generate_data_api(
133134
method="POST",

dbm-ui/backend/configuration/handlers/password.py

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,14 @@
2424
from backend.db_meta.models import Machine
2525
from backend.db_periodic_task.models import DBPeriodicTask
2626
from backend.db_services.ipchooser.query.resource import ResourceQueryHelper
27-
from backend.flow.consts import DEFAULT_INSTANCE
27+
from backend.db_services.taskflow.handlers import TaskFlowHandler
28+
from backend.flow.consts import DEFAULT_INSTANCE, FAILED_STATES, SUCCEED_STATES
29+
from backend.flow.engine.bamboo.engine import BambooEngine
30+
from backend.flow.engine.controller.mysql import MySQLController
31+
from backend.flow.models import FlowTree
32+
from backend.flow.plugins.components.collections.common.external_service import ExternalServiceComponent
33+
from backend.ticket.constants import TicketType
34+
from backend.utils.basic import generate_root_id
2835
from backend.utils.string import base64_decode, base64_encode
2936

3037
logger = logging.getLogger("root")
@@ -132,13 +139,21 @@ def query_admin_password(
132139
return admin_password_data
133140

134141
@classmethod
135-
def modify_admin_password(cls, operator: str, password: str, lock_hour: int, instance_list: List[Dict]):
142+
def modify_admin_password(
143+
cls,
144+
operator: str,
145+
password: str,
146+
lock_hour: int,
147+
instance_list: List[Dict],
148+
is_async: bool = False,
149+
):
136150
"""
137151
修改db的admin密码
138152
@param operator: 操作人
139153
@param password: 修改密码
140154
@param lock_hour: 锁定时长
141155
@param instance_list: 修改的实例列表
156+
@param is_async: 是否异步执行
142157
"""
143158
# 获取业务信息,任取一台machine查询
144159
machine = Machine.objects.get(bk_cloud_id=instance_list[0]["bk_cloud_id"], ip=instance_list[0]["ip"])
@@ -172,7 +187,7 @@ def modify_admin_password(cls, operator: str, password: str, lock_hour: int, ins
172187

173188
# 填充参数,修改admin的密码
174189
db_type = db_type.pop()
175-
modify_password_params = {
190+
params = {
176191
# username固定是ADMIN,与DBM_MYSQL_ADMIN_USER保持一致
177192
"username": DB_ADMIN_USER_MAP[db_type],
178193
"component": db_type,
@@ -183,11 +198,37 @@ def modify_admin_password(cls, operator: str, password: str, lock_hour: int, ins
183198
"security_rule_name": DBM_PASSWORD_SECURITY_NAME,
184199
"async": False,
185200
}
186-
data = DBPrivManagerApi.modify_admin_password(
187-
params=modify_password_params, raw=True, timeout=DBPrivManagerApi.TIMEOUT
188-
)["data"]
201+
202+
# 同步执行直接调用接口,异步执行则返回任务ID
203+
if not is_async:
204+
resp = DBPrivManagerApi.modify_admin_password(params=params, timeout=DBPrivManagerApi.TIMEOUT, raw=True)
205+
data = resp["data"]
206+
else:
207+
data = root_id = generate_root_id()
208+
params.update(ticket_type=TicketType.ADMIN_PASSWORD_MODIFY, bk_biz_id=bk_biz_id, created_by=operator)
209+
MySQLController(root_id=root_id, ticket_data=params).mysql_randomize_password()
189210
return data
190211

212+
@classmethod
213+
def query_async_modify_result(cls, root_id: str):
214+
"""
215+
查询异步密码修改结果
216+
@param root_id: 任务ID
217+
"""
218+
flow_tree = FlowTree.objects.get(root_id=root_id)
219+
# 任务未完成,退出
220+
if flow_tree.status not in [*FAILED_STATES, *SUCCEED_STATES]:
221+
return {"status": flow_tree.status, "data": ""}
222+
223+
# 查询修改密码的节点id
224+
task_handler = TaskFlowHandler(root_id)
225+
node_id = task_handler.get_node_id_by_component(flow_tree.tree, component_code=ExternalServiceComponent.code)[
226+
0
227+
]
228+
# 查询输出数据
229+
resp = BambooEngine(root_id).get_node_output_data(node_id).data["resp"]
230+
return {"status": flow_tree.status, "data": resp["data"]}
231+
191232
@classmethod
192233
def _get_password_role(cls, cluster_type, role):
193234
"""获取实例对应的密码角色"""

dbm-ui/backend/configuration/serializers.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ class InstanceInfoSerializer(serializers.Serializer):
118118
lock_hour = serializers.IntegerField(help_text=_("密码到期小时"))
119119
password = serializers.CharField(help_text=_("密码"))
120120
instance_list = serializers.ListSerializer(help_text=_("实例信息"), child=InstanceInfoSerializer())
121+
is_async = serializers.BooleanField(help_text=_("是否异步执行"), required=False, default=False)
121122

122123
def validate(self, attrs):
123124
# 校验密码中的特殊字符
@@ -133,6 +134,10 @@ def validate(self, attrs):
133134
return attrs
134135

135136

137+
class QueryAsyncModifyResultSerializer(serializers.Serializer):
138+
root_id = serializers.CharField(help_text=_("任务ID"))
139+
140+
136141
class PasswordPolicySerializer(serializers.Serializer):
137142
class PolicySerializer(serializers.Serializer):
138143
class IncludeRuleSerializer(serializers.Serializer):

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
ModifyAdminPasswordSerializer,
2929
ModifyMySQLPasswordRandomCycleSerializer,
3030
PasswordPolicySerializer,
31+
QueryAsyncModifyResultSerializer,
3132
VerifyPasswordResponseSerializer,
3233
VerifyPasswordSerializer,
3334
)
@@ -157,3 +158,13 @@ def modify_admin_password(self, request, *args, **kwargs):
157158
validated_data = self.params_validate(self.get_serializer_class())
158159
validated_data["operator"] = request.user.username
159160
return Response(DBPasswordHandler.modify_admin_password(**validated_data))
161+
162+
@common_swagger_auto_schema(
163+
operation_summary=_("查询异步密码修改执行结果"),
164+
request_body=QueryAsyncModifyResultSerializer(),
165+
tags=[SWAGGER_TAG],
166+
)
167+
@action(methods=["POST"], detail=False, serializer_class=QueryAsyncModifyResultSerializer)
168+
def query_async_modify_result(self, request, *args, **kwargs):
169+
validated_data = self.params_validate(self.get_serializer_class())
170+
return Response(DBPasswordHandler.query_async_modify_result(**validated_data))

dbm-ui/backend/flow/engine/bamboo/engine.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,10 @@ def get_node_input_data(self, node_id: str) -> EngineAPIResult:
101101
result = api.get_execution_data_inputs(runtime=BambooDjangoRuntime(), node_id=node_id)
102102
return result
103103

104+
def get_node_output_data(self, node_id: str) -> EngineAPIResult:
105+
result = api.get_execution_data_outputs(runtime=BambooDjangoRuntime(), node_id=node_id)
106+
return result
107+
104108
def get_node_histories(self, node_id: str) -> EngineAPIResult:
105109
result = api.get_node_histories(runtime=BambooDjangoRuntime(), node_id=node_id)
106110
return result

dbm-ui/backend/flow/engine/bamboo/scene/mysql/mysql_random_password.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424

2525
def random_password_callback(params, data, kwargs, global_data):
2626
"""密码随机化成功后的回调函数, 记录随机化成功和失败IP"""
27+
if not global_data.get("uid"):
28+
return
2729
flow = Ticket.objects.get(id=global_data["uid"]).current_flow()
2830
ticket_data = flow.details["ticket_data"]
2931
ticket_data.update(random_results=data)
@@ -55,6 +57,8 @@ def mysql_randomize_password(self):
5557
"api_import_module": "DBPrivManagerApi",
5658
"api_call_func": "modify_admin_password",
5759
"success_callback_path": f"{random_password_callback.__module__}.{random_password_callback.__name__}",
60+
"output": True,
61+
"raw": True,
5862
},
5963
)
6064
mysql_authorize_rules.run_pipeline()

dbm-ui/backend/flow/plugins/components/collections/common/external_service.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,14 @@ def _execute(self, data, parent_data):
3030
api_import_module: str = kwargs.get("api_import_module")
3131
api_call_func: str = kwargs.get("api_call_func")
3232
params: Dict[str, Any] = kwargs.get("params")
33+
# 是否返回原始请求
34+
raw = kwargs.get("raw", False)
3335

3436
external_service: Callable = getattr(
3537
getattr(importlib.import_module(api_import_path), api_import_module), api_call_func
3638
)
3739
try:
38-
resp = external_service(params)
40+
resp = external_service(params, raw=raw)
3941
self.log_info(_("第三方接口: {} 请求成功! 返回参数为: {}").format(f"{api_import_path}.{api_call_func}", resp))
4042
except (ApiResultError, ApiRequestError) as e:
4143
self.log_info(_("第三方接口:{} 调用失败!错误信息为: {}").format(f"{api_import_path}.{api_call_func}", e))
@@ -47,11 +49,15 @@ def _execute(self, data, parent_data):
4749
# 成功时调用回调函数
4850
success_callback = kwargs.get("success_callback_path")
4951
if success_callback:
50-
func_module, func_name = success_callback.rsplit(",", 1)
52+
func_module, func_name = success_callback.rsplit(".", 1)
5153
getattr(importlib.import_module(func_module), func_name)(
5254
params=params, data=resp, kwargs=kwargs, global_data=global_data
5355
)
5456

57+
# 存入返回响应data
58+
if kwargs.get("output"):
59+
data.outputs.resp = resp
60+
5561
return True
5662

5763

dbm-ui/backend/ticket/builders/redis/redis_toolbox_cut_off.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,19 @@
1818
from backend.db_services.dbbase.constants import IpSource
1919
from backend.flow.engine.controller.redis import RedisController
2020
from backend.ticket import builders
21-
from backend.ticket.builders.common.base import BaseOperateResourceParamBuilder, SkipToRepresentationMixin
21+
from backend.ticket.builders.common.base import (
22+
BaseOperateResourceParamBuilder,
23+
DisplayInfoSerializer,
24+
SkipToRepresentationMixin,
25+
)
2226
from backend.ticket.builders.redis.base import BaseRedisTicketFlowBuilder, ClusterValidateMixin
2327
from backend.ticket.constants import TicketType
2428

2529

2630
class RedisClusterCutOffDetailSerializer(SkipToRepresentationMixin, ClusterValidateMixin, serializers.Serializer):
2731
"""整机替换"""
2832

29-
class InfoSerializer(serializers.Serializer):
33+
class InfoSerializer(DisplayInfoSerializer):
3034
class HostInfoSerializer(serializers.Serializer):
3135
ip = serializers.IPAddressField()
3236
spec_id = serializers.IntegerField()
@@ -97,13 +101,10 @@ class RedisClusterCutOffFlowBuilder(BaseRedisTicketFlowBuilder):
97101

98102
def patch_ticket_detail(self):
99103
"""redis_master -> backend_group"""
100-
101-
super().patch_ticket_detail()
102-
103-
resource_spec = {}
104104
cluster_ids = list(itertools.chain(*[infos["cluster_ids"] for infos in self.ticket.details["infos"]]))
105105
id__cluster = {cluster.id: cluster for cluster in Cluster.objects.filter(id__in=cluster_ids)}
106106
for info in self.ticket.details["infos"]:
107+
resource_spec = {}
107108
# 取第一个cluster即可,即使是多集群,也是单机多实例的情况
108109
cluster = id__cluster[info["cluster_ids"][0]]
109110
for role in [
@@ -149,3 +150,4 @@ def patch_ticket_detail(self):
149150
info["resource_spec"] = resource_spec
150151

151152
self.ticket.save(update_fields=["details"])
153+
super().patch_ticket_detail()

dbm-ui/backend/ticket/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,7 @@ def get_cluster_type_by_ticket(cls, ticket_type):
459459

460460
# 资源池
461461
RESOURCE_IMPORT = EnumField("RESOURCE_IMPORT", _("资源池导入"))
462+
ADMIN_PASSWORD_MODIFY = EnumField("ADMIN_PASSWORD_MODIFY", _("临时密码修改"))
462463
# fmt: on
463464

464465
# VM

0 commit comments

Comments
 (0)