rucio 37.3.0__py3-none-any.whl → 37.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of rucio might be problematic. Click here for more details.

Files changed (97) hide show
  1. rucio/cli/rule.py +1 -1
  2. rucio/client/accountclient.py +205 -60
  3. rucio/client/accountlimitclient.py +84 -25
  4. rucio/client/baseclient.py +85 -48
  5. rucio/client/client.py +49 -41
  6. rucio/client/configclient.py +36 -13
  7. rucio/client/credentialclient.py +16 -6
  8. rucio/client/didclient.py +321 -133
  9. rucio/client/diracclient.py +13 -6
  10. rucio/client/downloadclient.py +435 -165
  11. rucio/client/exportclient.py +8 -2
  12. rucio/client/fileclient.py +10 -3
  13. rucio/client/importclient.py +4 -1
  14. rucio/client/lifetimeclient.py +48 -31
  15. rucio/client/lockclient.py +22 -7
  16. rucio/client/metaconventionsclient.py +59 -21
  17. rucio/client/pingclient.py +3 -1
  18. rucio/client/replicaclient.py +213 -96
  19. rucio/client/requestclient.py +123 -16
  20. rucio/client/rseclient.py +385 -160
  21. rucio/client/ruleclient.py +147 -51
  22. rucio/client/scopeclient.py +35 -10
  23. rucio/client/subscriptionclient.py +60 -27
  24. rucio/client/touchclient.py +16 -7
  25. rucio/core/permission/generic.py +37 -1
  26. rucio/core/replica.py +5 -5
  27. rucio/core/rule.py +5 -3
  28. rucio/daemons/judge/evaluator.py +1 -1
  29. rucio/gateway/replica.py +129 -41
  30. rucio/gateway/request.py +176 -103
  31. rucio/gateway/subscription.py +90 -108
  32. rucio/vcsversion.py +3 -3
  33. rucio/web/rest/flaskapi/v1/redirect.py +1 -1
  34. rucio/web/rest/flaskapi/v1/replicas.py +1 -1
  35. rucio/web/rest/flaskapi/v1/requests.py +211 -20
  36. rucio/web/rest/flaskapi/v1/subscriptions.py +9 -9
  37. {rucio-37.3.0.data → rucio-37.4.0.data}/data/rucio/etc/rucio.cfg.template +0 -1
  38. {rucio-37.3.0.data → rucio-37.4.0.data}/data/rucio/etc/rucio_multi_vo.cfg.template +0 -1
  39. {rucio-37.3.0.dist-info → rucio-37.4.0.dist-info}/METADATA +1 -1
  40. {rucio-37.3.0.dist-info → rucio-37.4.0.dist-info}/RECORD +97 -97
  41. {rucio-37.3.0.dist-info → rucio-37.4.0.dist-info}/WHEEL +1 -1
  42. {rucio-37.3.0.data → rucio-37.4.0.data}/data/rucio/etc/alembic.ini.template +0 -0
  43. {rucio-37.3.0.data → rucio-37.4.0.data}/data/rucio/etc/alembic_offline.ini.template +0 -0
  44. {rucio-37.3.0.data → rucio-37.4.0.data}/data/rucio/etc/globus-config.yml.template +0 -0
  45. {rucio-37.3.0.data → rucio-37.4.0.data}/data/rucio/etc/ldap.cfg.template +0 -0
  46. {rucio-37.3.0.data → rucio-37.4.0.data}/data/rucio/etc/mail_templates/rule_approval_request.tmpl +0 -0
  47. {rucio-37.3.0.data → rucio-37.4.0.data}/data/rucio/etc/mail_templates/rule_approved_admin.tmpl +0 -0
  48. {rucio-37.3.0.data → rucio-37.4.0.data}/data/rucio/etc/mail_templates/rule_approved_user.tmpl +0 -0
  49. {rucio-37.3.0.data → rucio-37.4.0.data}/data/rucio/etc/mail_templates/rule_denied_admin.tmpl +0 -0
  50. {rucio-37.3.0.data → rucio-37.4.0.data}/data/rucio/etc/mail_templates/rule_denied_user.tmpl +0 -0
  51. {rucio-37.3.0.data → rucio-37.4.0.data}/data/rucio/etc/mail_templates/rule_ok_notification.tmpl +0 -0
  52. {rucio-37.3.0.data → rucio-37.4.0.data}/data/rucio/etc/rse-accounts.cfg.template +0 -0
  53. {rucio-37.3.0.data → rucio-37.4.0.data}/data/rucio/etc/rucio.cfg.atlas.client.template +0 -0
  54. {rucio-37.3.0.data → rucio-37.4.0.data}/data/rucio/requirements.server.txt +0 -0
  55. {rucio-37.3.0.data → rucio-37.4.0.data}/data/rucio/tools/bootstrap.py +0 -0
  56. {rucio-37.3.0.data → rucio-37.4.0.data}/data/rucio/tools/merge_rucio_configs.py +0 -0
  57. {rucio-37.3.0.data → rucio-37.4.0.data}/data/rucio/tools/reset_database.py +0 -0
  58. {rucio-37.3.0.data → rucio-37.4.0.data}/scripts/rucio +0 -0
  59. {rucio-37.3.0.data → rucio-37.4.0.data}/scripts/rucio-abacus-account +0 -0
  60. {rucio-37.3.0.data → rucio-37.4.0.data}/scripts/rucio-abacus-collection-replica +0 -0
  61. {rucio-37.3.0.data → rucio-37.4.0.data}/scripts/rucio-abacus-rse +0 -0
  62. {rucio-37.3.0.data → rucio-37.4.0.data}/scripts/rucio-admin +0 -0
  63. {rucio-37.3.0.data → rucio-37.4.0.data}/scripts/rucio-atropos +0 -0
  64. {rucio-37.3.0.data → rucio-37.4.0.data}/scripts/rucio-auditor +0 -0
  65. {rucio-37.3.0.data → rucio-37.4.0.data}/scripts/rucio-automatix +0 -0
  66. {rucio-37.3.0.data → rucio-37.4.0.data}/scripts/rucio-bb8 +0 -0
  67. {rucio-37.3.0.data → rucio-37.4.0.data}/scripts/rucio-cache-client +0 -0
  68. {rucio-37.3.0.data → rucio-37.4.0.data}/scripts/rucio-cache-consumer +0 -0
  69. {rucio-37.3.0.data → rucio-37.4.0.data}/scripts/rucio-conveyor-finisher +0 -0
  70. {rucio-37.3.0.data → rucio-37.4.0.data}/scripts/rucio-conveyor-poller +0 -0
  71. {rucio-37.3.0.data → rucio-37.4.0.data}/scripts/rucio-conveyor-preparer +0 -0
  72. {rucio-37.3.0.data → rucio-37.4.0.data}/scripts/rucio-conveyor-receiver +0 -0
  73. {rucio-37.3.0.data → rucio-37.4.0.data}/scripts/rucio-conveyor-stager +0 -0
  74. {rucio-37.3.0.data → rucio-37.4.0.data}/scripts/rucio-conveyor-submitter +0 -0
  75. {rucio-37.3.0.data → rucio-37.4.0.data}/scripts/rucio-conveyor-throttler +0 -0
  76. {rucio-37.3.0.data → rucio-37.4.0.data}/scripts/rucio-dark-reaper +0 -0
  77. {rucio-37.3.0.data → rucio-37.4.0.data}/scripts/rucio-dumper +0 -0
  78. {rucio-37.3.0.data → rucio-37.4.0.data}/scripts/rucio-follower +0 -0
  79. {rucio-37.3.0.data → rucio-37.4.0.data}/scripts/rucio-hermes +0 -0
  80. {rucio-37.3.0.data → rucio-37.4.0.data}/scripts/rucio-judge-cleaner +0 -0
  81. {rucio-37.3.0.data → rucio-37.4.0.data}/scripts/rucio-judge-evaluator +0 -0
  82. {rucio-37.3.0.data → rucio-37.4.0.data}/scripts/rucio-judge-injector +0 -0
  83. {rucio-37.3.0.data → rucio-37.4.0.data}/scripts/rucio-judge-repairer +0 -0
  84. {rucio-37.3.0.data → rucio-37.4.0.data}/scripts/rucio-kronos +0 -0
  85. {rucio-37.3.0.data → rucio-37.4.0.data}/scripts/rucio-minos +0 -0
  86. {rucio-37.3.0.data → rucio-37.4.0.data}/scripts/rucio-minos-temporary-expiration +0 -0
  87. {rucio-37.3.0.data → rucio-37.4.0.data}/scripts/rucio-necromancer +0 -0
  88. {rucio-37.3.0.data → rucio-37.4.0.data}/scripts/rucio-oauth-manager +0 -0
  89. {rucio-37.3.0.data → rucio-37.4.0.data}/scripts/rucio-reaper +0 -0
  90. {rucio-37.3.0.data → rucio-37.4.0.data}/scripts/rucio-replica-recoverer +0 -0
  91. {rucio-37.3.0.data → rucio-37.4.0.data}/scripts/rucio-rse-decommissioner +0 -0
  92. {rucio-37.3.0.data → rucio-37.4.0.data}/scripts/rucio-storage-consistency-actions +0 -0
  93. {rucio-37.3.0.data → rucio-37.4.0.data}/scripts/rucio-transmogrifier +0 -0
  94. {rucio-37.3.0.data → rucio-37.4.0.data}/scripts/rucio-undertaker +0 -0
  95. {rucio-37.3.0.dist-info → rucio-37.4.0.dist-info}/licenses/AUTHORS.rst +0 -0
  96. {rucio-37.3.0.dist-info → rucio-37.4.0.dist-info}/licenses/LICENSE +0 -0
  97. {rucio-37.3.0.dist-info → rucio-37.4.0.dist-info}/top_level.txt +0 -0
@@ -13,18 +13,18 @@
13
13
  # limitations under the License.
14
14
 
15
15
  import json
16
- from typing import TYPE_CHECKING
16
+ from typing import TYPE_CHECKING, Union, cast
17
17
 
18
18
  import flask
19
19
  from flask import Flask, Response
20
20
 
21
- from rucio.common.exception import RequestNotFound
21
+ from rucio.common.exception import AccessDenied, RequestNotFound
22
22
  from rucio.common.utils import APIEncoder, render_json
23
23
  from rucio.core.rse import get_rses_with_attribute_value
24
- from rucio.db.sqla.constants import RequestState
24
+ from rucio.db.sqla.constants import RequestState, TransferLimitDirection
25
25
  from rucio.gateway import request
26
26
  from rucio.web.rest.flaskapi.authenticated_bp import AuthenticatedBlueprint
27
- from rucio.web.rest.flaskapi.v1.common import ErrorHandlingMethodView, check_accept_header_wrapper_flask, generate_http_error_flask, parse_scope_name, response_headers, try_stream
27
+ from rucio.web.rest.flaskapi.v1.common import ErrorHandlingMethodView, check_accept_header_wrapper_flask, generate_http_error_flask, json_parameters, param_get, parse_scope_name, response_headers, try_stream
28
28
 
29
29
  if TYPE_CHECKING:
30
30
  from collections.abc import Iterator
@@ -178,7 +178,7 @@ class RequestGet(ErrorHandlingMethodView):
178
178
  description: Not acceptable
179
179
  """
180
180
  try:
181
- scope, name = parse_scope_name(scope_name, flask.request.environ.get('vo'))
181
+ scope, name = parse_scope_name(scope_name, flask.request.environ['vo'])
182
182
  except ValueError as error:
183
183
  return generate_http_error_flask(400, error)
184
184
 
@@ -187,8 +187,8 @@ class RequestGet(ErrorHandlingMethodView):
187
187
  scope=scope,
188
188
  name=name,
189
189
  rse=rse,
190
- issuer=flask.request.environ.get('issuer'),
191
- vo=flask.request.environ.get('vo'),
190
+ issuer=flask.request.environ['issuer'],
191
+ vo=flask.request.environ['vo'],
192
192
  )
193
193
  return Response(json.dumps(request_data, cls=APIEncoder), content_type='application/json')
194
194
  except RequestNotFound as error:
@@ -343,7 +343,7 @@ class RequestHistoryGet(ErrorHandlingMethodView):
343
343
  description: Not acceptable
344
344
  """
345
345
  try:
346
- scope, name = parse_scope_name(scope_name, flask.request.environ.get('vo'))
346
+ scope, name = parse_scope_name(scope_name, flask.request.environ['vo'])
347
347
  except ValueError as error:
348
348
  return generate_http_error_flask(400, error)
349
349
 
@@ -352,8 +352,8 @@ class RequestHistoryGet(ErrorHandlingMethodView):
352
352
  scope=scope,
353
353
  name=name,
354
354
  rse=rse,
355
- issuer=flask.request.environ.get('issuer'),
356
- vo=flask.request.environ.get('vo'),
355
+ issuer=flask.request.environ['issuer'],
356
+ vo=flask.request.environ['vo'],
357
357
  )
358
358
  return Response(json.dumps(request_data, cls=APIEncoder), content_type='application/json')
359
359
  except RequestNotFound as error:
@@ -564,11 +564,11 @@ class RequestList(ErrorHandlingMethodView):
564
564
  src_rses = []
565
565
  dst_rses = []
566
566
  if src_site:
567
- src_rses = get_rses_with_attribute_value(key='site', value=src_site, vo=flask.request.environ.get('vo'))
567
+ src_rses = get_rses_with_attribute_value(key='site', value=src_site, vo=flask.request.environ['vo'])
568
568
  if not src_rses:
569
569
  return generate_http_error_flask(404, 'NotFound', f'Could not resolve site name {src_site} to RSE')
570
570
  src_rses = [rse['rse_name'] for rse in src_rses]
571
- dst_rses = get_rses_with_attribute_value(key='site', value=dst_site, vo=flask.request.environ.get('vo'))
571
+ dst_rses = get_rses_with_attribute_value(key='site', value=dst_site, vo=flask.request.environ['vo'])
572
572
  if not dst_rses:
573
573
  return generate_http_error_flask(404, 'NotFound', f'Could not resolve site name {dst_site} to RSE')
574
574
  dst_rses = [rse['rse_name'] for rse in dst_rses]
@@ -576,11 +576,15 @@ class RequestList(ErrorHandlingMethodView):
576
576
  dst_rses = [dst_rse]
577
577
  src_rses = [src_rse]
578
578
 
579
+ # Manual cast to list[str] as static code analysis erroneously sees these as list[Optional[str]]
580
+ src_rses = cast("list[str]", src_rses)
581
+ dst_rses = cast("list[str]", dst_rses)
582
+
579
583
  def generate(issuer, vo):
580
584
  for result in request.list_requests(src_rses, dst_rses, states, issuer=issuer, vo=vo):
581
585
  yield render_json(**result) + '\n'
582
586
 
583
- return try_stream(generate(issuer=flask.request.environ.get('issuer'), vo=flask.request.environ.get('vo')))
587
+ return try_stream(generate(issuer=flask.request.environ['issuer'], vo=flask.request.environ['vo']))
584
588
 
585
589
 
586
590
  class RequestHistoryList(ErrorHandlingMethodView):
@@ -779,8 +783,8 @@ class RequestHistoryList(ErrorHandlingMethodView):
779
783
  src_site = flask.request.args.get('src_site', default=None)
780
784
  dst_site = flask.request.args.get('dst_site', default=None)
781
785
  request_states = flask.request.args.get('request_states', default=None)
782
- offset = flask.request.args.get('offset', default=0)
783
- limit = flask.request.args.get('limit', default=100)
786
+ offset = flask.request.args.get('offset', type=int, default=0)
787
+ limit = flask.request.args.get('limit', type=int, default=100)
784
788
 
785
789
  if not request_states:
786
790
  return generate_http_error_flask(400, 'MissingParameter', 'Request state is missing')
@@ -801,11 +805,11 @@ class RequestHistoryList(ErrorHandlingMethodView):
801
805
  src_rses = []
802
806
  dst_rses = []
803
807
  if src_site:
804
- src_rses = get_rses_with_attribute_value(key='site', value=src_site, vo=flask.request.environ.get('vo'))
808
+ src_rses = get_rses_with_attribute_value(key='site', value=src_site, vo=flask.request.environ['vo'])
805
809
  if not src_rses:
806
810
  return generate_http_error_flask(404, 'NotFound', f'Could not resolve site name {src_site} to RSE')
807
811
  src_rses = [rse['rse_name'] for rse in src_rses]
808
- dst_rses = get_rses_with_attribute_value(key='site', value=dst_site, vo=flask.request.environ.get('vo'))
812
+ dst_rses = get_rses_with_attribute_value(key='site', value=dst_site, vo=flask.request.environ['vo'])
809
813
  if not dst_rses:
810
814
  return generate_http_error_flask(404, 'NotFound', f'Could not resolve site name {dst_site} to RSE')
811
815
  dst_rses = [rse['rse_name'] for rse in dst_rses]
@@ -813,11 +817,15 @@ class RequestHistoryList(ErrorHandlingMethodView):
813
817
  dst_rses = [dst_rse]
814
818
  src_rses = [src_rse]
815
819
 
820
+ # Manual cast to list[str] as static code analysis erroneously sees these as list[Optional[str]]
821
+ src_rses = cast("list[str]", src_rses)
822
+ dst_rses = cast("list[str]", dst_rses)
823
+
816
824
  def generate(issuer, vo):
817
825
  for result in request.list_requests_history(src_rses, dst_rses, states, issuer=issuer, vo=vo, offset=offset, limit=limit):
818
826
  yield render_json(**result) + '\n'
819
827
 
820
- return try_stream(generate(issuer=flask.request.environ.get('issuer'), vo=flask.request.environ.get('vo')))
828
+ return try_stream(generate(issuer=flask.request.environ['issuer'], vo=flask.request.environ['vo']))
821
829
 
822
830
 
823
831
  class RequestMetricsGet(ErrorHandlingMethodView):
@@ -960,8 +968,8 @@ class RequestMetricsGet(ErrorHandlingMethodView):
960
968
  src_rse=src_rse,
961
969
  activity=activity,
962
970
  group_by_rse_attribute=group_by_rse_attribute,
963
- issuer=flask.request.environ.get('issuer'),
964
- vo=flask.request.environ.get('vo')
971
+ issuer=flask.request.environ['issuer'],
972
+ vo=flask.request.environ['vo']
965
973
  )
966
974
 
967
975
  if format == 'panda':
@@ -972,6 +980,187 @@ class RequestMetricsGet(ErrorHandlingMethodView):
972
980
  yield render_json(**result) + '\n'
973
981
  return try_stream(generate())
974
982
 
983
+ class TransferLimits(ErrorHandlingMethodView):
984
+ """ REST API to get, set or delete transfer limits. """
985
+
986
+ @check_accept_header_wrapper_flask(['application/x-json-stream'])
987
+ def get(self) -> flask.Response:
988
+ """
989
+ ---
990
+ summary: Get Transfer Limits
991
+ description: Get all the transfer limits.
992
+ tags:
993
+ - Requests
994
+ responses:
995
+ 200:
996
+ description: OK
997
+ content:
998
+ application/x-json-stream:
999
+ schema:
1000
+ description: All the transfer limits
1001
+ type: array
1002
+ items:
1003
+ type: object
1004
+ properties:
1005
+ id:
1006
+ description: The transfer limit id.
1007
+ type: string
1008
+ rse_expression:
1009
+ description: The RSE expression for which the limit applies.
1010
+ type: string
1011
+ direction:
1012
+ description: The direction in which this limit applies (source/destination)
1013
+ type: string
1014
+ max_transfers:
1015
+ description: Maximum number of transfers allowed.
1016
+ type: integer
1017
+ volume:
1018
+ description: Maximum transfer volume in bytes.
1019
+ type: integer
1020
+ deadline:
1021
+ description: Maximum waiting time in hours until a datasets gets released.
1022
+ type: integer
1023
+ strategy:
1024
+ description: defines how to handle datasets: `fifo` (each file released separately) or `grouped_fifo` (wait for the entire dataset to fit)
1025
+ type: string
1026
+ transfers:
1027
+ description: Current number of active transfers
1028
+ type: integer
1029
+ waitings:
1030
+ description: Current number of waiting transfers
1031
+ type: integer
1032
+ updated_at:
1033
+ description: Datetime of the last update.
1034
+ type: string
1035
+ created_at:
1036
+ description: Datetime of the creation of the transfer limit.
1037
+ type: string
1038
+ 401:
1039
+ description: Invalid Auth Token
1040
+ """
1041
+ transfer_limits = request.list_transfer_limits(issuer=flask.request.environ['issuer'], vo=flask.request.environ['vo'])
1042
+ def generate() -> "Iterator[str]":
1043
+ for limit in transfer_limits:
1044
+ yield json.dumps(limit, cls=APIEncoder) + '\n'
1045
+ return try_stream(generate())
1046
+
1047
+ def put(self) -> Union[flask.Response, tuple[str, int]]:
1048
+ """
1049
+ ---
1050
+ summary: Set Transfer Limit
1051
+ description: Create or update a transfer limit for a specific RSE expression and activity.
1052
+ tags:
1053
+ - Requests
1054
+ requestBody:
1055
+ content:
1056
+ application/json:
1057
+ schema:
1058
+ type: object
1059
+ required:
1060
+ - rse_expression
1061
+ - max_transfers
1062
+ properties:
1063
+ rse_expression:
1064
+ type: string
1065
+ description: The RSE expression for which the transfer limit is being set.
1066
+ activity:
1067
+ type: string
1068
+ description: The activity to which the transfer limit applies.
1069
+ max_transfers:
1070
+ type: integer
1071
+ description: The maximum number of transfers allowed.
1072
+ direction:
1073
+ type: string
1074
+ description: The direction of the transfer limit (source or destination).
1075
+ enum: ["SOURCE", "DESTINATION"]
1076
+ default: "DESTINATION"
1077
+ volume:
1078
+ type: integer
1079
+ description: The maximum transfer volume in bytes.
1080
+ deadline:
1081
+ type: integer
1082
+ description: The maximum waiting time in hours until a dataset is released.
1083
+ strategy:
1084
+ type: string
1085
+ description: The strategy for handling datasets (e.g., `fifo` or `grouped_fifo`).
1086
+ transfers:
1087
+ type: integer
1088
+ description: The current number of active transfers.
1089
+ waitings:
1090
+ type: integer
1091
+ description: The current number of waiting transfers.
1092
+ responses:
1093
+ 201:
1094
+ description: Transfer limit set successfully.
1095
+ 400:
1096
+ description: Invalid input data.
1097
+ 401:
1098
+ description: Invalid Auth Token.
1099
+ 500:
1100
+ description: Internal server error.
1101
+ """
1102
+ parameters = json_parameters()
1103
+ rse_expression = param_get(parameters, 'rse_expression')
1104
+ max_transfers = param_get(parameters, 'max_transfers')
1105
+
1106
+ try:
1107
+ request.set_transfer_limit(
1108
+ rse_expression=rse_expression,
1109
+ max_transfers=max_transfers,
1110
+ activity=param_get(parameters, 'activity', default=None),
1111
+ direction=param_get(parameters, 'direction', default=TransferLimitDirection.DESTINATION),
1112
+ volume=param_get(parameters, 'volume', default=None),
1113
+ deadline=param_get(parameters, 'deadline', default=None),
1114
+ strategy=param_get(parameters, 'strategy', default=None),
1115
+ transfers=param_get(parameters, 'transfers', default=None),
1116
+ waitings=param_get(parameters, 'waitings', default=None),
1117
+ issuer=flask.request.environ['issuer'],
1118
+ vo=flask.request.environ['vo']
1119
+ )
1120
+ except AccessDenied as error:
1121
+ return generate_http_error_flask(401, error)
1122
+
1123
+ return '', 201
1124
+
1125
+ def delete(self) -> Union[flask.Response, tuple[str, int]]:
1126
+ """
1127
+ ---
1128
+ summary: Delete Transfer Limit
1129
+ description: Delete a transfer limit for an RSE expression.
1130
+ tags:
1131
+ - Requests
1132
+ parameters:
1133
+ - name: rse_expression
1134
+ in: query
1135
+ description: The RSE expression to delete the limit for.
1136
+ required: true
1137
+ schema:
1138
+ type: string
1139
+ responses:
1140
+ 200:
1141
+ description: Transfer limit deleted successfully.
1142
+ 400:
1143
+ description: Invalid input data.
1144
+ 401:
1145
+ description: Invalid Auth Token.
1146
+ 500:
1147
+ description: Internal server error.
1148
+ """
1149
+ parameters = json_parameters()
1150
+ rse_expression = param_get(parameters, 'rse_expression')
1151
+
1152
+ try:
1153
+ request.delete_transfer_limit(
1154
+ rse_expression=rse_expression,
1155
+ activity=param_get(parameters, 'activity', default=None),
1156
+ direction=param_get(parameters, 'direction', default=TransferLimitDirection.DESTINATION),
1157
+ issuer=flask.request.environ['issuer'],
1158
+ vo=flask.request.environ['vo']
1159
+ )
1160
+ except AccessDenied as error:
1161
+ return generate_http_error_flask(401, error)
1162
+
1163
+ return '', 200
975
1164
 
976
1165
  def blueprint():
977
1166
  bp = AuthenticatedBlueprint('requests', __name__, url_prefix='/requests')
@@ -986,6 +1175,8 @@ def blueprint():
986
1175
  bp.add_url_rule('/history/list', view_func=request_history_list_view, methods=['get', ])
987
1176
  request_metrics_view = RequestMetricsGet.as_view('request_metrics_get')
988
1177
  bp.add_url_rule('/metrics', view_func=request_metrics_view, methods=['get', ])
1178
+ transfer_limits_view = TransferLimits.as_view('transfer_limits_get')
1179
+ bp.add_url_rule('/transfer_limits', view_func=transfer_limits_view, methods=['get', 'put', 'delete'])
989
1180
 
990
1181
  bp.after_request(response_headers)
991
1182
  return bp
@@ -112,7 +112,7 @@ class Subscription(ErrorHandlingMethodView):
112
112
  for subscription in list_subscriptions(name=name, account=account, vo=vo):
113
113
  yield render_json(**subscription) + '\n'
114
114
 
115
- return try_stream(generate(vo=request.environ.get('vo')))
115
+ return try_stream(generate(vo=request.environ['vo']))
116
116
  except SubscriptionNotFound as error:
117
117
  return generate_http_error_flask(404, error)
118
118
 
@@ -193,7 +193,7 @@ class Subscription(ErrorHandlingMethodView):
193
193
  metadata[keyword] = param_get(options, keyword, default=metadata[keyword])
194
194
 
195
195
  try:
196
- update_subscription(name=name, account=account, metadata=metadata, issuer=request.environ.get('issuer'), vo=request.environ.get('vo'))
196
+ update_subscription(name=name, account=account, metadata=metadata, issuer=request.environ['issuer'], vo=request.environ['vo'])
197
197
  except (InvalidObject, TypeError) as error:
198
198
  return generate_http_error_flask(400, InvalidObject.__name__, error.args[0])
199
199
  except AccessDenied as error:
@@ -306,8 +306,8 @@ class Subscription(ErrorHandlingMethodView):
306
306
  retroactive=retroactive,
307
307
  dry_run=dry_run,
308
308
  priority=priority,
309
- issuer=request.environ.get('issuer'),
310
- vo=request.environ.get('vo'),
309
+ issuer=request.environ['issuer'],
310
+ vo=request.environ['vo'],
311
311
  )
312
312
  except (InvalidObject, TypeError) as error:
313
313
  return generate_http_error_flask(400, InvalidObject.__name__, error.args[0])
@@ -400,7 +400,7 @@ class SubscriptionName(ErrorHandlingMethodView):
400
400
  for subscription in list_subscriptions(name=name, vo=vo):
401
401
  yield render_json(**subscription) + '\n'
402
402
 
403
- return try_stream(generate(vo=request.environ.get('vo')))
403
+ return try_stream(generate(vo=request.environ['vo']))
404
404
  except SubscriptionNotFound as error:
405
405
  return generate_http_error_flask(404, error)
406
406
 
@@ -454,7 +454,7 @@ class Rules(ErrorHandlingMethodView):
454
454
  """
455
455
  state = request.args.get('state', default=None)
456
456
  try:
457
- subscriptions = [subscription['id'] for subscription in list_subscriptions(name=name, account=account, vo=request.environ.get('vo'))]
457
+ subscriptions = [subscription['id'] for subscription in list_subscriptions(name=name, account=account, vo=request.environ['vo'])]
458
458
 
459
459
  def generate(vo):
460
460
  if len(subscriptions) > 0:
@@ -465,7 +465,7 @@ class Rules(ErrorHandlingMethodView):
465
465
  for rule in list_replication_rules({'subscription_id': subscriptions[0]}, vo=vo):
466
466
  yield render_json(**rule) + '\n'
467
467
 
468
- return try_stream(generate(vo=request.environ.get('vo')))
468
+ return try_stream(generate(vo=request.environ['vo']))
469
469
  except (RuleNotFound, SubscriptionNotFound) as error:
470
470
  return generate_http_error_flask(404, error)
471
471
 
@@ -529,7 +529,7 @@ class States(ErrorHandlingMethodView):
529
529
  for row in list_subscription_rule_states(name=name, account=account, vo=vo):
530
530
  yield dumps(row, cls=APIEncoder) + '\n'
531
531
 
532
- return try_stream(generate(vo=request.environ.get('vo')))
532
+ return try_stream(generate(vo=request.environ['vo']))
533
533
 
534
534
 
535
535
  class SubscriptionId(ErrorHandlingMethodView):
@@ -607,7 +607,7 @@ class SubscriptionId(ErrorHandlingMethodView):
607
607
  description: Not acceptable
608
608
  """
609
609
  try:
610
- subscription = get_subscription_by_id(subscription_id, vo=request.environ.get('vo'))
610
+ subscription = get_subscription_by_id(subscription_id, vo=request.environ['vo'])
611
611
  except SubscriptionNotFound as error:
612
612
  return generate_http_error_flask(404, error)
613
613
 
@@ -116,7 +116,6 @@ metrics_port = 8080
116
116
  [conveyor]
117
117
  scheme = srm,gsiftp,root,http,https
118
118
  transfertool = fts3
119
- ftshosts = https://fts3-pilot.cern.ch:8446, https://fts3-pilot.cern.ch:8446
120
119
  cacert = /opt/rucio/etc/web/ca.crt
121
120
  usercert = /opt/rucio/tools/x509up
122
121
 
@@ -97,7 +97,6 @@ metrics_port = 8080
97
97
  [conveyor]
98
98
  scheme = srm,gsiftp,root,http,https
99
99
  transfertool = fts3
100
- ftshosts = https://fts3-pilot.cern.ch:8446, https://fts3-pilot.cern.ch:8446
101
100
  cacert = /opt/rucio/etc/web/ca.crt
102
101
  usercert = /opt/rucio/tools/x509up
103
102
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rucio
3
- Version: 37.3.0
3
+ Version: 37.4.0
4
4
  Summary: Rucio Package
5
5
  Home-page: https://rucio.cern.ch/
6
6
  Author: Rucio