This is an automated email from the ASF dual-hosted git repository. morningman pushed a commit to branch branch-1.2-unstable in repository https://gitbox.apache.org/repos/asf/doris.git
commit 76b3c2b4a44950eaa96a1399140d07b7b4757a6c Author: Mingyu Chen <morning...@163.com> AuthorDate: Tue Nov 8 20:39:01 2022 +0800 [improvement](profile) support ordinary user to get query profile via http api (#14016) --- .../fe/manager/query-profile-action.md | 101 ++++++++++++++++++++ .../fe/manager/query-profile-action.md | 102 +++++++++++++++++++++ .../apache/doris/common/util/ProfileManager.java | 25 ++++- .../httpv2/rest/manager/QueryProfileAction.java | 63 ++++++++++--- 4 files changed, 277 insertions(+), 14 deletions(-) diff --git a/docs/en/docs/admin-manual/http-actions/fe/manager/query-profile-action.md b/docs/en/docs/admin-manual/http-actions/fe/manager/query-profile-action.md index 0b86949483..efce4a26b8 100644 --- a/docs/en/docs/admin-manual/http-actions/fe/manager/query-profile-action.md +++ b/docs/en/docs/admin-manual/http-actions/fe/manager/query-profile-action.md @@ -30,6 +30,8 @@ under the License. `GET /rest/v2/manager/query/query_info` +`GET /rest/v2/manager/query/trace/{trace_id}` + `GET /rest/v2/manager/query/sql/{query_id}` `GET /rest/v2/manager/query/profile/text/{query_id}` @@ -96,6 +98,12 @@ Gets information about select queries for all fe nodes in the cluster. } ``` +<version since="1.2"> + +Admin 和 Root 用户可以查看所有 Query。普通用户仅能查看自己发送的 Query。 + +</version> + ### Examples ``` GET /rest/v2/manager/query/query_info @@ -135,6 +143,54 @@ GET /rest/v2/manager/query/query_info } ``` +## Get Query Id By Trace Id + +`GET /rest/v2/manager/query/trace_id/{trace_id}` + +### Description + +Get query id by trance id. + +Before executing a Query, set a unique trace id: + +`set set session_context="trace_id:your_trace_id";` + +After executing the Query within the same Session, the query id can be obtained through the trace id. + +### Path parameters + +* `{trace_id}` + + User specific trace id. + +### Query parameters + +### Response + +``` +{ + "msg": "success", + "code": 0, + "data": "fb1d9737de914af1-a498d5c5dec638d3", + "count": 0 +} +``` + +<version since="1.2"> + +Admin and Root user can view all queries. Ordinary users can only view the Query sent by themselves. If the specified trace id does not exist or has no permission, it will return Bad Request: + +``` +{ + "msg": "Bad Request", + "code": 403, + "data": "error messages", + "count": 0 +} +``` + +</version> + ## Get the sql and text profile for the specified query `GET /rest/v2/manager/query/sql/{query_id}` @@ -180,6 +236,21 @@ Get the sql and profile text for the specified query id. "count": 0 } ``` + +<version since="1.2"> + +Admin and Root user can view all queries. Ordinary users can only view the Query sent by themselves. If the specified trace id does not exist or has no permission, it will return Bad Request: + +``` +{ + "msg": "Bad Request", + "code": 403, + "data": "error messages", + "count": 0 +} +``` + +</version> ### Examples @@ -237,6 +308,21 @@ Get the fragment name, instance id and execution time for the specified query id "count": 0 } ``` + +<version since="1.2"> + +Admin and Root user can view all queries. Ordinary users can only view the Query sent by themselves. If the specified trace id does not exist or has no permission, it will return Bad Request: + +``` +{ + "msg": "Bad Request", + "code": 403, + "data": "error messages", + "count": 0 +} +``` + +</version> ### Examples @@ -313,6 +399,21 @@ Get the tree profile information of the specified query id, same as `show query } ``` +<version since="1.2"> + +Admin and Root user can view all queries. Ordinary users can only view the Query sent by themselves. If the specified trace id does not exist or has no permission, it will return Bad Request: + +``` +{ + "msg": "Bad Request", + "code": 403, + "data": "error messages", + "count": 0 +} +``` + +</version> + ## Current running queries `GET /rest/v2/manager/query/current_queries` diff --git a/docs/zh-CN/docs/admin-manual/http-actions/fe/manager/query-profile-action.md b/docs/zh-CN/docs/admin-manual/http-actions/fe/manager/query-profile-action.md index 5283107279..3954d129cc 100644 --- a/docs/zh-CN/docs/admin-manual/http-actions/fe/manager/query-profile-action.md +++ b/docs/zh-CN/docs/admin-manual/http-actions/fe/manager/query-profile-action.md @@ -30,6 +30,8 @@ under the License. `GET /rest/v2/manager/query/query_info` +`GET /rest/v2/manager/query/trace/{trace_id}` + `GET /rest/v2/manager/query/sql/{query_id}` `GET /rest/v2/manager/query/profile/text/{query_id}` @@ -96,6 +98,12 @@ under the License. } ``` +<version since="1.2"> + +Admin 和 Root 用户可以查看所有 Query。普通用户仅能查看自己发送的 Query。 + +</version> + ### Examples ``` GET /rest/v2/manager/query/query_info @@ -135,6 +143,54 @@ GET /rest/v2/manager/query/query_info } ``` +## 通过 Trace Id 获取 Query Id + +`GET /rest/v2/manager/query/trace_id/{trace_id}` + +### Description + +通过 Trace Id 获取 Query Id. + +在执行一个 Query 前,先设置一个唯一的 trace id: + +`set set session_context="trace_id:your_trace_id";` + +在同一个 Session 链接内执行 Query 后,可以通过 trace id 获取 query id。 + +### Path parameters + +* `{trace_id}` + + 用户设置的 trace id. + +### Query parameters + +### Response + +``` +{ + "msg": "success", + "code": 0, + "data": "fb1d9737de914af1-a498d5c5dec638d3", + "count": 0 +} +``` + +<version since="1.2"> + +Admin 和 Root 用户可以查看所有 Query。普通用户仅能查看自己发送的 Query。若指定 trace id 不存在或无权限,则返回 Bad Request: + +``` +{ + "msg": "Bad Request", + "code": 403, + "data": "error messages", + "count": 0 +} +``` + +</version> + ## 获取指定查询的sql和文本profile `GET /rest/v2/manager/query/sql/{query_id}` @@ -180,6 +236,21 @@ GET /rest/v2/manager/query/query_info "count": 0 } ``` + +<version since="1.2"> + +Admin 和 Root 用户可以查看所有 Query。普通用户仅能查看自己发送的 Query。若指定 query id 不存在或无权限,则返回 Bad Request: + +``` +{ + "msg": "Bad Request", + "code": 403, + "data": "error messages", + "count": 0 +} +``` + +</version> ### Examples @@ -237,6 +308,21 @@ GET /rest/v2/manager/query/query_info "count": 0 } ``` + +<version since="1.2"> + +Admin 和 Root 用户可以查看所有 Query。普通用户仅能查看自己发送的 Query。若指定 query id 不存在或无权限,则返回 Bad Request: + +``` +{ + "msg": "Bad Request", + "code": 403, + "data": "error messages", + "count": 0 +} +``` + +</version> ### Examples @@ -313,6 +399,21 @@ GET /rest/v2/manager/query/query_info } ``` +<version since="1.2"> + +Admin 和 Root 用户可以查看所有 Query。普通用户仅能查看自己发送的 Query。若指定 query id 不存在或无权限,则返回 Bad Request: + +``` +{ + "msg": "Bad Request", + "code": 403, + "data": "error messages", + "count": 0 +} +``` + +</version> + ## 正在执行的query `GET /rest/v2/manager/query/current_queries` @@ -372,3 +473,4 @@ GET /rest/v2/manager/query/query_info "count": 0 } ``` + diff --git a/fe/fe-core/src/main/java/org/apache/doris/common/util/ProfileManager.java b/fe/fe-core/src/main/java/org/apache/doris/common/util/ProfileManager.java index 1dc0125707..3922c496c7 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/common/util/ProfileManager.java +++ b/fe/fe-core/src/main/java/org/apache/doris/common/util/ProfileManager.java @@ -18,7 +18,9 @@ package org.apache.doris.common.util; import org.apache.doris.common.AnalysisException; +import org.apache.doris.common.AuthenticationException; import org.apache.doris.common.Config; +import org.apache.doris.common.DdlException; import org.apache.doris.common.profile.MultiProfileTreeBuilder; import org.apache.doris.common.profile.ProfileTreeBuilder; import org.apache.doris.common.profile.ProfileTreeNode; @@ -208,13 +210,34 @@ public class ProfileManager { if (element == null) { return null; } - return element.profileContent; } finally { readLock.unlock(); } } + /** + * Check if the query with specific query id is queried by specific user. + * + * @param user + * @param queryId + * @throws DdlException + */ + public void checkAuthByUserAndQueryId(String user, String queryId) throws AuthenticationException { + readLock.lock(); + try { + ProfileElement element = queryIdToProfileMap.get(queryId); + if (element == null) { + throw new AuthenticationException("query with id " + queryId + " not found"); + } + if (!element.infoStrings.get(USER).equals(user)) { + throw new AuthenticationException("Access deny to view query with id: " + queryId); + } + } finally { + readLock.unlock(); + } + } + public ProfileTreeNode getFragmentProfileTree(String queryID, String executionId) throws AnalysisException { MultiProfileTreeBuilder builder; readLock.lock(); diff --git a/fe/fe-core/src/main/java/org/apache/doris/httpv2/rest/manager/QueryProfileAction.java b/fe/fe-core/src/main/java/org/apache/doris/httpv2/rest/manager/QueryProfileAction.java index b395990f74..f4cbcf76f9 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/httpv2/rest/manager/QueryProfileAction.java +++ b/fe/fe-core/src/main/java/org/apache/doris/httpv2/rest/manager/QueryProfileAction.java @@ -19,6 +19,7 @@ package org.apache.doris.httpv2.rest.manager; import org.apache.doris.catalog.Env; import org.apache.doris.common.AnalysisException; +import org.apache.doris.common.AuthenticationException; import org.apache.doris.common.Config; import org.apache.doris.common.Pair; import org.apache.doris.common.proc.CurrentQueryStatementsProcNode; @@ -28,6 +29,7 @@ import org.apache.doris.common.profile.ProfileTreePrinter; import org.apache.doris.common.util.ProfileManager; import org.apache.doris.httpv2.entity.ResponseEntityBuilder; import org.apache.doris.httpv2.rest.RestBaseController; +import org.apache.doris.mysql.privilege.PaloAuth; import org.apache.doris.mysql.privilege.PrivPredicate; import org.apache.doris.persist.gson.GsonUtils; import org.apache.doris.qe.ConnectContext; @@ -59,6 +61,7 @@ import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import java.util.stream.Stream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -133,7 +136,6 @@ public class QueryProfileAction extends RestBaseController { @RequestParam(value = IS_ALL_NODE_PARA, required = false, defaultValue = "true") boolean isAllNode) { executeCheckPassword(request, response); - checkGlobalAuth(ConnectContext.get().getCurrentUserIdentity(), PrivPredicate.ADMIN); List<List<String>> queries = Lists.newArrayList(); if (isAllNode) { @@ -166,11 +168,10 @@ public class QueryProfileAction extends RestBaseController { return ResponseEntityBuilder.ok(new NodeAction.NodeInfo(QUERY_TITLE_NAMES, queries)); } - queries = ProfileManager.getInstance().getAllQueries().stream() - .filter(profile -> profile.get(4).equals("Query")).collect(Collectors.toList()); - if (!Strings.isNullOrEmpty(queryId)) { - queries = queries.stream().filter(q -> q.get(0).equals(queryId)).collect(Collectors.toList()); - } + Stream<List<String>> queryStream = ProfileManager.getInstance().getAllQueries().stream() + .filter(profile -> profile.get(4).equals("Query")); + queryStream = filterQueriesByUserAndQueryId(queryStream, queryId); + queries = queryStream.collect(Collectors.toList()); // add node information for (List<String> query : queries) { @@ -200,7 +201,6 @@ public class QueryProfileAction extends RestBaseController { @RequestParam(value = IS_ALL_NODE_PARA, required = false, defaultValue = "true") boolean isAllNode) { executeCheckPassword(request, response); - checkGlobalAuth(ConnectContext.get().getCurrentUserIdentity(), PrivPredicate.ADMIN); Map<String, String> querySql = Maps.newHashMap(); if (isAllNode) { @@ -219,9 +219,9 @@ public class QueryProfileAction extends RestBaseController { } } } else { - List<List<String>> queries = - ProfileManager.getInstance().getAllQueries().stream().filter(query -> query.get(0).equals(queryId)) - .collect(Collectors.toList()); + Stream<List<String>> queryStream = ProfileManager.getInstance().getAllQueries().stream(); + queryStream = filterQueriesByUserAndQueryId(queryStream, queryId); + List<List<String>> queries = queryStream.collect(Collectors.toList()); if (!queries.isEmpty()) { querySql.put("sql", queries.get(0).get(3)); } @@ -229,6 +229,20 @@ public class QueryProfileAction extends RestBaseController { return ResponseEntityBuilder.ok(querySql); } + private Stream<List<String>> filterQueriesByUserAndQueryId(Stream<List<String>> queryStream, String queryId) { + // filter by user + // Only admin or root user can see all profile. + // Common user can only review the query of their own. + String user = ConnectContext.get().getCurrentUserIdentity().getQualifiedUser(); + if (!user.equalsIgnoreCase(PaloAuth.ADMIN_USER) && !user.equalsIgnoreCase(PaloAuth.ROOT_USER)) { + queryStream = queryStream.filter(q -> q.get(1).equals(user)); + } + if (!Strings.isNullOrEmpty(queryId)) { + queryStream = queryStream.filter(query -> query.get(0).equals(queryId)); + } + return queryStream; + } + /** * Returns the text profile for the specified query id. * There are 3 formats: @@ -251,7 +265,12 @@ public class QueryProfileAction extends RestBaseController { @RequestParam(value = INSTANCE_ID, required = false) String instanceId, @RequestParam(value = IS_ALL_NODE_PARA, required = false, defaultValue = "true") boolean isAllNode) { executeCheckPassword(request, response); - checkGlobalAuth(ConnectContext.get().getCurrentUserIdentity(), PrivPredicate.ADMIN); + + try { + checkAuthByUserAndQueryId(queryId); + } catch (AuthenticationException e) { + return ResponseEntityBuilder.badRequest(e.getMessage()); + } if (format.equals("text")) { return getTextProfile(request, queryId, isAllNode); @@ -278,7 +297,6 @@ public class QueryProfileAction extends RestBaseController { @PathVariable("trace_id") String traceId, @RequestParam(value = IS_ALL_NODE_PARA, required = false, defaultValue = "true") boolean isAllNode) { executeCheckPassword(request, response); - checkGlobalAuth(ConnectContext.get().getCurrentUserIdentity(), PrivPredicate.ADMIN); if (isAllNode) { String httpPath = "/rest/v2/manager/query/trace_id/" + traceId; @@ -305,6 +323,13 @@ public class QueryProfileAction extends RestBaseController { if (Strings.isNullOrEmpty(queryId)) { return ResponseEntityBuilder.badRequest("Not found"); } + + try { + checkAuthByUserAndQueryId(queryId); + } catch (AuthenticationException e) { + return ResponseEntityBuilder.badRequest(e.getMessage()); + } + return ResponseEntityBuilder.ok(queryId); } return ResponseEntityBuilder.badRequest("not found query id"); @@ -315,7 +340,6 @@ public class QueryProfileAction extends RestBaseController { @PathVariable("query_id") String queryId, @RequestParam(value = IS_ALL_NODE_PARA, required = false, defaultValue = "true") boolean isAllNode) { executeCheckPassword(request, response); - checkGlobalAuth(ConnectContext.get().getCurrentUserIdentity(), PrivPredicate.ADMIN); if (isAllNode) { String httpPath = "/rest/v2/manager/query/profile/fragments/" + queryId; @@ -337,6 +361,12 @@ public class QueryProfileAction extends RestBaseController { } } } else { + try { + checkAuthByUserAndQueryId(queryId); + } catch (AuthenticationException e) { + return ResponseEntityBuilder.badRequest(e.getMessage()); + } + try { return ResponseEntityBuilder.ok(ProfileManager.getInstance().getFragmentsAndInstances(queryId)); } catch (AnalysisException e) { @@ -439,6 +469,13 @@ public class QueryProfileAction extends RestBaseController { return ResponseEntityBuilder.ok(result); } + private void checkAuthByUserAndQueryId(String queryId) throws AuthenticationException { + String user = ConnectContext.get().getCurrentUserIdentity().getQualifiedUser(); + if (!user.equalsIgnoreCase(PaloAuth.ADMIN_USER) && !user.equalsIgnoreCase(PaloAuth.ROOT_USER)) { + ProfileManager.getInstance().checkAuthByUserAndQueryId(user, queryId); + } + } + /** * return the result of CurrentQueryStatementsProcNode. * --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@doris.apache.org For additional commands, e-mail: commits-h...@doris.apache.org