diff --git a/lib/rucio/api/request.py b/lib/rucio/api/request.py index b3585fec7d..c4eaf57ec5 100644 --- a/lib/rucio/api/request.py +++ b/lib/rucio/api/request.py @@ -199,7 +199,6 @@ def list_requests(src_rses, dst_rses, states, issuer, vo='def', *, session: "Ses def list_requests_history(src_rses, dst_rses, states, issuer, vo='def', offset=None, limit=None, *, session: "Session"): """ List all historical requests in a specific state from a source RSE to a destination RSE. - :param src_rses: source RSEs. :param dst_rses: destination RSEs. :param states: list of request states. @@ -218,3 +217,21 @@ def list_requests_history(src_rses, dst_rses, states, issuer, vo='def', offset=N for req in request.list_requests_history(src_rse_ids, dst_rse_ids, states, offset, limit, session=session): req = req.to_dict() yield api_update_return_dict(req, session=session) + + +@stream_session +def get_request_stats(state, issuer, vo='def', *, session: "Session"): + """ + Get statistics of requests in a specific state grouped by source RSE, destination RSE, and activity. + + :param state: request state. + :param issuer: Issuing account as a string. + :param session: The database session in use. + """ + kwargs = {'issuer': issuer} + if not permission.has_permission(issuer=issuer, vo=vo, action='get_request_stats', kwargs=kwargs, session=session): + raise exception.AccessDenied(f'{issuer} cannot get request statistics') + + for req in request.get_request_stats(state, session=session): + req = req.to_dict() + yield api_update_return_dict(req, session=session) diff --git a/lib/rucio/web/rest/flaskapi/v1/requests.py b/lib/rucio/web/rest/flaskapi/v1/requests.py index 8e84b3841e..a5f77d4d15 100644 --- a/lib/rucio/web/rest/flaskapi/v1/requests.py +++ b/lib/rucio/web/rest/flaskapi/v1/requests.py @@ -365,7 +365,7 @@ class RequestList(ErrorHandlingMethodView): def get(self): """ --- - summary: List Historic Requests + summary: List Requests description: List requests for a given source and destination RSE or site. tags: - Requests @@ -818,6 +818,87 @@ def generate(issuer, vo): return try_stream(generate(issuer=flask.request.environ.get('issuer'), vo=flask.request.environ.get('vo'))) +class RequestStatsGet(ErrorHandlingMethodView): + """ REST API to get request stats. """ + + @check_accept_header_wrapper_flask(['application/x-json-stream']) + def get(self): + """ + --- + summary: Get Request Statistics + description: Get statistics of requests grouped by source, destination, and activity. + tags: + - Requests + parameters: + - name: state + in: query + description: The accepted request state. + schema: + type: string + responses: + 200: + description: OK + content: + application/x-json-stream: + schema: + description: Statistics of requests by source, destination, and activity. + type: array + items: + description: Statistics of the request group for a given (source, destination, activity) tuple. + type: object + properties: + dest_rse_id: + description: The destination RSE id. + type: string + source_rse_id: + description: The source RSE id. + type: string + activity: + description: The activity of the request group. + type: string + state: + description: The state of the request group. + type: string + enum: ["Q", "G", "S", "F", "D", "L", "N", "O", "A", "M", "U", "W", "P"] + source_rse: + description: The name of the source RSE. + type: string + dest_rse: + description: The name of the destination RSE. + type: string + account: + description: The account which issued the request group. + type: string + counter: + description: The number of requests in this request group. + type: number + bytes: + description: The total transferred bytes of requests in this request group. + type: number + 401: + description: Invalid Auth Token + 404: + description: Not found + 406: + description: Not acceptable + """ + state = flask.request.args.get('state', default=None) + + if not state: + return generate_http_error_flask(400, 'MissingParameter', 'Request state is missing') + + try: + request_state = RequestState(state) + except ValueError: + return generate_http_error_flask(400, 'Invalid', 'Request state value is invalid') + + def generate(issuer, vo): + for result in request.get_request_stats(request_state, issuer=issuer, vo=vo): + yield render_json(**result) + '\n' + + return try_stream(generate(issuer=flask.request.environ.get('issuer'), vo=flask.request.environ.get('vo'))) + + def blueprint(): bp = AuthenticatedBlueprint('requests', __name__, url_prefix='/requests') @@ -829,6 +910,8 @@ def blueprint(): bp.add_url_rule('/list', view_func=request_list_view, methods=['get', ]) request_history_list_view = RequestHistoryList.as_view('request_history_list') bp.add_url_rule('/history/list', view_func=request_history_list_view, methods=['get', ]) + request_stats_view = RequestStatsGet.as_view('request_stats') + bp.add_url_rule('/stats', view_func=request_stats_view, methods=['get', ]) bp.after_request(response_headers) return bp