Skip to content

Commit e1355c0

Browse files
committed
feat(backend): mongodb webconsole查询 #10199
# Reviewed, transaction id: 39764
1 parent 94b40e5 commit e1355c0

File tree

9 files changed

+196
-4
lines changed

9 files changed

+196
-4
lines changed

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,5 +141,15 @@ def __init__(self):
141141
description=_("mysql rpc 复杂接口"),
142142
)
143143

144+
self.mongodb_rpc = ProxyAPI(
145+
method="POST",
146+
base=self.BASE_DOMAIN,
147+
url="mongodb/rpc",
148+
module=self.MODULE,
149+
ssl=ssl_flag,
150+
description=_("mongodb 远程执行"),
151+
default_timeout=self.DRS_TIMEOUT,
152+
)
153+
144154

145155
DRSApi = _DRSApi()

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from backend.db_services.ipchooser.query.resource import ResourceQueryHelper
2424
from backend.db_services.redis.resources.redis_cluster.query import RedisListRetrieveResource
2525
from backend.dbm_init.constants import CC_APP_ABBR_ATTR
26+
from backend.ticket.builders.common.field import DBTimezoneField
2627
from backend.ticket.constants import TicketType
2728

2829

@@ -188,6 +189,8 @@ class WebConsoleSerializer(serializers.Serializer):
188189
# redis 额外参数
189190
db_num = serializers.IntegerField(help_text=_("数据库编号(redis 额外参数)"), required=False)
190191
raw = serializers.BooleanField(help_text=_("源编码(redis 额外参数)"), required=False)
192+
# mongodb 额外参数
193+
session_time = DBTimezoneField(help_text=_("会话创建时间(mongodb 额外参数)"), required=False)
191194

192195

193196
class DBConsoleSerializer(serializers.Serializer):

dbm-ui/backend/db_services/dbbase/views.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
WebConsoleSerializer,
5656
)
5757
from backend.db_services.ipchooser.query.resource import ResourceQueryHelper
58+
from backend.db_services.mongodb.handlers import RemoteMongoDBServiceHandler
5859
from backend.db_services.mysql.remote_service.handlers import RemoteServiceHandler
5960
from backend.db_services.redis.toolbox.handlers import ToolboxHandler
6061
from backend.iam_app.handlers.drf_perm.base import DBManagePermission
@@ -295,7 +296,10 @@ def webconsole(self, request):
295296
# redis
296297
elif db_type in ClusterType.redis_cluster_types():
297298
data = ToolboxHandler.webconsole_rpc(**data)
298-
299+
# mongodb
300+
elif db_type == DBType.MongoDB:
301+
data["user_id"] = request.user.id
302+
data = RemoteMongoDBServiceHandler.webconsole_rpc(**data)
299303
# 对外部查询进行数据脱敏
300304
if getattr(request, "is_external", False) and env.BKDATA_DATA_TOKEN:
301305
data = BKBaseApi.data_desensitization(text=json.dumps(data), bk_biz_id=cluster.bk_biz_id)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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+
"""
11+
12+
from datetime import datetime
13+
14+
from django.utils import timezone
15+
16+
from backend.components import DRSApi
17+
from backend.db_meta.exceptions import ClusterNotExistException, InstanceNotExistException
18+
from backend.db_services.mysql.remote_service.handlers import RemoteServiceHandler
19+
from backend.exceptions import ApiResultError
20+
from backend.flow.utils.mongodb.mongodb_util import MongoUtil
21+
22+
23+
class RemoteMongoDBServiceHandler(RemoteServiceHandler):
24+
@staticmethod
25+
def webconsole_rpc(cluster_id: int, cmd: str, **kwargs):
26+
"""
27+
执行webconsole命令,只支持select语句
28+
@param cluster_id: 集群ID
29+
@param cmd: 执行命令
30+
"""
31+
# 获取rpc结果
32+
try:
33+
session_time = kwargs.get("session_time", datetime.now(timezone.utc).replace(microsecond=0))
34+
session = f"{kwargs['user_id']}:{session_time}"
35+
rpc_results = DRSApi.mongodb_rpc(
36+
MongoUtil.get_mongodb_webconsole_args(cluster_id=cluster_id, session=session, command=cmd)
37+
)
38+
except (ApiResultError, InstanceNotExistException, ClusterNotExistException) as err:
39+
return {"query": "", "error_msg": err.message}
40+
41+
return {"query": rpc_results, "error_msg": ""}

dbm-ui/backend/db_services/mongodb/resources/views.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,12 @@ class MongoDBViewSet(ResourceViewSet):
101101
query_serializer_class = serializers.ListMongoDBResourceSLZ
102102
list_instances_slz = serializers.MongoDBListInstancesSerializer
103103

104-
list_perm_actions = [ActionEnum.MONGODB_VIEW, ActionEnum.MONGODB_ENABLE_DISABLE, ActionEnum.MONGODB_DESTROY]
104+
list_perm_actions = [
105+
ActionEnum.MONGODB_VIEW,
106+
ActionEnum.MONGODB_ENABLE_DISABLE,
107+
ActionEnum.MONGODB_DESTROY,
108+
ActionEnum.MONGODB_WEBCONSOLE,
109+
]
105110
list_instance_perm_actions = [ActionEnum.MONGODB_VIEW]
106111

107112
@common_swagger_auto_schema(

dbm-ui/backend/flow/consts.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1474,6 +1474,7 @@ class MongoDBManagerUser(str, StructuredEnum):
14741474
AppDbaUser = EnumField("appdba", _("appdba"))
14751475
MonitorUser = EnumField("monitor", _("monitor"))
14761476
AppMonitorUser = EnumField("appmonitor", _("appmonitor"))
1477+
WebconsoleUser = EnumField("_webconsoleuser", _("_webconsoleuser"))
14771478

14781479

14791480
class MongoDBUserPrivileges(str, StructuredEnum):

dbm-ui/backend/flow/utils/mongodb/mongodb_repo.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ def from_instance(cls, s: Union[ProxyInstance, StorageInstance], with_domain: bo
4040
node = MongoNode(s.ip_port.split(":")[0], s.port, meta_role, s.machine.bk_cloud_id, s.machine_type, domain)
4141
return node
4242

43+
def addr(self) -> str:
44+
return "{}:{}".format(self.ip, self.port)
45+
4346
def equal(self, other: "MongoNode") -> bool:
4447
return self.ip == other.ip and self.port == other.port and self.bk_cloud_id == other.bk_cloud_id
4548

@@ -432,14 +435,14 @@ def fetch_many_cluster(cls, with_domain: bool, **kwargs):
432435
return rows
433436

434437
@classmethod
435-
def fetch_one_cluster(cls, withDomain: bool, **kwargs):
438+
def fetch_one_cluster(cls, withDomain: bool, **kwargs) -> MongoDBCluster:
436439
rows = cls.fetch_many_cluster(withDomain, **kwargs)
437440
if len(rows) > 0:
438441
return rows[0]
439442
return None
440443

441444
@classmethod
442-
def fetch_many_cluster_dict(cls, withDomain: bool = False, **kwargs):
445+
def fetch_many_cluster_dict(cls, withDomain: bool = False, **kwargs) -> dict[int, MongoDBCluster]:
443446
clusters = cls.fetch_many_cluster(withDomain, **kwargs)
444447
clusters_map = {}
445448
for cluster in clusters:

dbm-ui/backend/flow/utils/mongodb/mongodb_util.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from backend.flow.consts import ConfigFileEnum, ConfigTypeEnum, MongoDBManagerUser, NameSpaceEnum
1010
from backend.flow.utils.mongodb import mongodb_password
1111
from backend.flow.utils.mongodb.mongodb_module_operate import MongoDBCCTopoOperator
12+
from backend.flow.utils.mongodb.mongodb_repo import MongoRepository
1213

1314
logger = logging.getLogger("flow")
1415

@@ -93,3 +94,111 @@ def update_instance_labels(*cluster_id_list: int):
9394
MongoDBCCTopoOperator(cluster).transfer_instances_to_cluster_module(proxy_objs, is_increment=True)
9495

9596
logger.info("cluster_id:{} update instance labels success".format(cluster_id))
97+
98+
@staticmethod
99+
def get_mongodb_webconsole_args(cluster_id: int, session: str, command: str, timeout: int = 15):
100+
"""
101+
获取mongodb webconsole参数
102+
@param cluster_id: 集群id
103+
@param session: session,用于标识一个请求,请务必保证带有用户id信息.
104+
@param command: 命令,如:show dbs, 首次连接可以为空
105+
@param timeout: 超时时间,单位秒
106+
"""
107+
cluster = MongoRepository().fetch_one_cluster(withDomain=False, id=cluster_id)
108+
if not cluster:
109+
raise Exception("cluster_id:{} not found".format(cluster_id))
110+
111+
# 获得连接的节点, 集群为mongos节点,副本集优先使用backup节点,没有backup节点则为m1节点
112+
connect_nodes = []
113+
if cluster.is_sharded_cluster():
114+
connect_nodes = cluster.get_mongos()[:2] # 取两个mongos就行
115+
else:
116+
shard = cluster.get_shards()[0]
117+
node = shard.get_backup_node()
118+
if node:
119+
connect_nodes.append(node)
120+
else:
121+
connect_nodes.append(shard.get_not_backup_nodes()[0])
122+
123+
if len(connect_nodes) == 0:
124+
raise Exception("cluster_id:{} can not get connect node".format(cluster_id))
125+
126+
# ip port
127+
node = connect_nodes[0]
128+
adminUserName = MongoDBManagerUser.DbaUser.value
129+
password_out = mongodb_password.MongoDBPassword().get_password_from_db(
130+
node.ip, node.port, node.bk_cloud_id, adminUserName
131+
)
132+
133+
if not password_out or "password" not in password_out:
134+
raise Exception(
135+
"can not get webconsole_user password for {}({}:{})".format(cluster_id, node.ip, node.port)
136+
)
137+
else:
138+
adminPassword = password_out["password"]
139+
140+
user, pwd, is_created = MongoUtil.cluster_pwd_get_or_create(
141+
cluster_id=cluster_id, bk_cloud_id=node.bk_cloud_id, username=MongoDBManagerUser.WebconsoleUser.value
142+
)
143+
144+
logger.info(
145+
"cluster_id:{} webconsole user: {}, password: {}, is_created: {}".format(cluster_id, user, pwd, is_created)
146+
)
147+
148+
return {
149+
"cluster_id": cluster.cluster_id,
150+
"cluster_type": cluster.cluster_type,
151+
"cluster_domain": cluster.immute_domain,
152+
"version": cluster.major_version.split("-")[-1],
153+
"addresses": [m.addr() for m in connect_nodes], # 取两个mongos就行
154+
"set_name": cluster.name,
155+
"command": command,
156+
"timeout": timeout,
157+
"admin_username": adminUserName,
158+
"admin_password": adminPassword,
159+
"username": user,
160+
"password": pwd,
161+
"session": "{}:{}".format(cluster_id, session),
162+
}
163+
164+
@staticmethod
165+
def cluster_pwd_get_or_create(cluster_id: int, bk_cloud_id: int, username: str) -> (str, str, bool):
166+
"""
167+
从密码库中获取或生成mongodb用户密码
168+
按mongo:+cluster_id为主键获取密码, 密码规则为mongodb_password
169+
返回 值为 (username, password, is_created)
170+
"""
171+
is_created = False
172+
cluster = "mongodb_cluster:{}".format(cluster_id)
173+
out = mongodb_password.MongoDBPassword().get_password_from_db(cluster, int(0), bk_cloud_id, username)
174+
# 接口返回异常
175+
if not out or "password" not in out:
176+
raise Exception("can not get dba_user password for {}:{}".format(cluster_id, bk_cloud_id))
177+
178+
# 如果密码为空,需要创建密码
179+
if out["password"] is None or out["password"] == "":
180+
new_pwd = mongodb_password.MongoDBPassword().create_user_password()
181+
if new_pwd["password"] is None:
182+
raise Exception("create password fail, error:{}".format(new_pwd["info"]))
183+
# 密码长度小于8位,表示创建失败. 我们的密码规则不会允许小于8位的密码
184+
if len(new_pwd["password"]) < 8:
185+
raise Exception("create password fail, password length is {}".format(len(new_pwd["password"])))
186+
err_msg = mongodb_password.MongoDBPassword().save_password_to_db2(
187+
instances=[
188+
{
189+
"ip": cluster,
190+
"port": 0,
191+
"bk_cloud_id": bk_cloud_id,
192+
}
193+
],
194+
username=username,
195+
password=new_pwd["password"],
196+
operator="admin",
197+
)
198+
199+
if err_msg != "":
200+
raise Exception("save password to db fail, error:{}".format(err_msg))
201+
out["password"] = new_pwd["password"]
202+
is_created = True
203+
204+
return username, out["password"], is_created

dbm-ui/backend/iam_app/dataclass/actions.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1534,6 +1534,22 @@ class ActionEnum:
15341534
common_labels=[CommonActionLabel.BIZ_MAINTAIN],
15351535
)
15361536

1537+
MONGODB_WEBCONSOLE = ActionMeta(
1538+
id="mongodb_webconsole",
1539+
name=_("MongoDB Webconsole执行"),
1540+
name_en="mongodb_webconsole",
1541+
type="execute",
1542+
related_actions=[DB_MANAGE.id],
1543+
related_resource_types=[ResourceEnum.MONGODB],
1544+
group=_("MongoDB"),
1545+
subgroup=_("集群管理"),
1546+
common_labels=[
1547+
CommonActionLabel.BIZ_READ_ONLY,
1548+
CommonActionLabel.BIZ_MAINTAIN,
1549+
CommonActionLabel.EXTERNAL_DEVELOPER
1550+
],
1551+
)
1552+
15371553
SQLSERVER_VIEW = ActionMeta(
15381554
id="sqlserver_view",
15391555
name=_("SQLServer 集群详情查看"),

0 commit comments

Comments
 (0)