rucio 37.2.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.
- rucio/cli/rule.py +1 -1
- rucio/client/accountclient.py +205 -60
- rucio/client/accountlimitclient.py +84 -25
- rucio/client/baseclient.py +85 -48
- rucio/client/client.py +49 -41
- rucio/client/configclient.py +36 -13
- rucio/client/credentialclient.py +16 -6
- rucio/client/didclient.py +321 -133
- rucio/client/diracclient.py +13 -6
- rucio/client/downloadclient.py +435 -165
- rucio/client/exportclient.py +8 -2
- rucio/client/fileclient.py +10 -3
- rucio/client/importclient.py +4 -1
- rucio/client/lifetimeclient.py +48 -31
- rucio/client/lockclient.py +22 -7
- rucio/client/metaconventionsclient.py +59 -21
- rucio/client/pingclient.py +3 -1
- rucio/client/replicaclient.py +213 -96
- rucio/client/requestclient.py +123 -16
- rucio/client/rseclient.py +385 -160
- rucio/client/ruleclient.py +147 -51
- rucio/client/scopeclient.py +35 -10
- rucio/client/subscriptionclient.py +60 -27
- rucio/client/touchclient.py +16 -7
- rucio/common/plugins.py +1 -1
- rucio/core/did.py +2 -3
- rucio/core/permission/generic.py +37 -1
- rucio/core/replica.py +6 -6
- rucio/core/rule.py +5 -3
- rucio/daemons/judge/evaluator.py +1 -1
- rucio/db/sqla/util.py +1 -1
- rucio/gateway/authentication.py +58 -88
- rucio/gateway/config.py +63 -75
- rucio/gateway/did.py +245 -329
- rucio/gateway/dirac.py +33 -34
- rucio/gateway/exporter.py +27 -30
- rucio/gateway/importer.py +12 -14
- rucio/gateway/lifetime_exception.py +16 -24
- rucio/gateway/lock.py +27 -40
- rucio/gateway/replica.py +334 -249
- rucio/gateway/request.py +176 -103
- rucio/gateway/rse.py +191 -218
- rucio/gateway/rule.py +115 -146
- rucio/gateway/scope.py +18 -25
- rucio/gateway/subscription.py +90 -108
- rucio/gateway/trace.py +48 -0
- rucio/vcsversion.py +3 -3
- rucio/web/rest/flaskapi/v1/accounts.py +2 -2
- rucio/web/rest/flaskapi/v1/auth.py +15 -0
- rucio/web/rest/flaskapi/v1/common.py +3 -0
- rucio/web/rest/flaskapi/v1/config.py +7 -7
- rucio/web/rest/flaskapi/v1/dids.py +55 -55
- rucio/web/rest/flaskapi/v1/dirac.py +2 -2
- rucio/web/rest/flaskapi/v1/export.py +1 -1
- rucio/web/rest/flaskapi/v1/import.py +1 -1
- rucio/web/rest/flaskapi/v1/lifetime_exceptions.py +5 -5
- rucio/web/rest/flaskapi/v1/locks.py +4 -4
- rucio/web/rest/flaskapi/v1/main.py +17 -10
- rucio/web/rest/flaskapi/v1/redirect.py +1 -1
- rucio/web/rest/flaskapi/v1/replicas.py +30 -29
- rucio/web/rest/flaskapi/v1/requests.py +211 -20
- rucio/web/rest/flaskapi/v1/rses.py +37 -37
- rucio/web/rest/flaskapi/v1/rules.py +15 -15
- rucio/web/rest/flaskapi/v1/scopes.py +3 -3
- rucio/web/rest/flaskapi/v1/subscriptions.py +9 -9
- rucio/web/rest/flaskapi/v1/traces.py +75 -77
- {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/etc/rucio.cfg.template +0 -1
- {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/etc/rucio_multi_vo.cfg.template +0 -1
- {rucio-37.2.0.dist-info → rucio-37.4.0.dist-info}/METADATA +1 -1
- {rucio-37.2.0.dist-info → rucio-37.4.0.dist-info}/RECORD +127 -126
- {rucio-37.2.0.dist-info → rucio-37.4.0.dist-info}/WHEEL +1 -1
- {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/etc/alembic.ini.template +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/etc/alembic_offline.ini.template +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/etc/globus-config.yml.template +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/etc/ldap.cfg.template +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/etc/mail_templates/rule_approval_request.tmpl +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/etc/mail_templates/rule_approved_admin.tmpl +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/etc/mail_templates/rule_approved_user.tmpl +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/etc/mail_templates/rule_denied_admin.tmpl +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/etc/mail_templates/rule_denied_user.tmpl +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/etc/mail_templates/rule_ok_notification.tmpl +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/etc/rse-accounts.cfg.template +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/etc/rucio.cfg.atlas.client.template +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/requirements.server.txt +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/tools/bootstrap.py +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/tools/merge_rucio_configs.py +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/tools/reset_database.py +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-abacus-account +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-abacus-collection-replica +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-abacus-rse +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-admin +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-atropos +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-auditor +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-automatix +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-bb8 +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-cache-client +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-cache-consumer +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-conveyor-finisher +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-conveyor-poller +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-conveyor-preparer +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-conveyor-receiver +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-conveyor-stager +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-conveyor-submitter +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-conveyor-throttler +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-dark-reaper +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-dumper +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-follower +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-hermes +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-judge-cleaner +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-judge-evaluator +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-judge-injector +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-judge-repairer +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-kronos +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-minos +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-minos-temporary-expiration +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-necromancer +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-oauth-manager +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-reaper +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-replica-recoverer +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-rse-decommissioner +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-storage-consistency-actions +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-transmogrifier +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-undertaker +0 -0
- {rucio-37.2.0.dist-info → rucio-37.4.0.dist-info}/licenses/AUTHORS.rst +0 -0
- {rucio-37.2.0.dist-info → rucio-37.4.0.dist-info}/licenses/LICENSE +0 -0
- {rucio-37.2.0.dist-info → rucio-37.4.0.dist-info}/top_level.txt +0 -0
|
@@ -226,7 +226,7 @@ class Replicas(ErrorHandlingMethodView):
|
|
|
226
226
|
description: Not acceptable
|
|
227
227
|
"""
|
|
228
228
|
try:
|
|
229
|
-
scope, name = parse_scope_name(scope_name, request.environ
|
|
229
|
+
scope, name = parse_scope_name(scope_name, request.environ['vo'])
|
|
230
230
|
except ValueError as error:
|
|
231
231
|
return generate_http_error_flask(400, error)
|
|
232
232
|
|
|
@@ -252,7 +252,7 @@ class Replicas(ErrorHandlingMethodView):
|
|
|
252
252
|
def _list_and_sort_replicas(vo):
|
|
253
253
|
# we need to call list_replicas before starting to reply
|
|
254
254
|
# otherwise the exceptions won't be propagated correctly
|
|
255
|
-
for rfile in list_replicas(dids=dids, schemes=schemes, vo=vo):
|
|
255
|
+
for rfile in list_replicas(dids=dids, schemes=schemes, vo=vo): # type: ignore (pending https://github.com/rucio/rucio/issues/7739)
|
|
256
256
|
replicas = []
|
|
257
257
|
dictreplica = {}
|
|
258
258
|
for rse in rfile['rses']:
|
|
@@ -264,7 +264,7 @@ class Replicas(ErrorHandlingMethodView):
|
|
|
264
264
|
rfile['pfns'] = dict(_sorted_with_priorities(rfile['pfns'], replicas, limit=limit))
|
|
265
265
|
yield rfile
|
|
266
266
|
|
|
267
|
-
rfiles = _list_and_sort_replicas(vo=request.environ
|
|
267
|
+
rfiles = _list_and_sort_replicas(vo=request.environ['vo'])
|
|
268
268
|
if metalink:
|
|
269
269
|
response_generator = _generate_metalink_response(rfiles, 'atlas', detailed_url=False)
|
|
270
270
|
else:
|
|
@@ -359,8 +359,8 @@ class Replicas(ErrorHandlingMethodView):
|
|
|
359
359
|
add_replicas(
|
|
360
360
|
rse=rse,
|
|
361
361
|
files=files,
|
|
362
|
-
issuer=request.environ
|
|
363
|
-
vo=request.environ
|
|
362
|
+
issuer=request.environ['issuer'],
|
|
363
|
+
vo=request.environ['vo'],
|
|
364
364
|
ignore_availability=param_get(parameters, 'ignore_availability', default=False),
|
|
365
365
|
)
|
|
366
366
|
except InvalidPath as error:
|
|
@@ -432,7 +432,7 @@ class Replicas(ErrorHandlingMethodView):
|
|
|
432
432
|
files = param_get(parameters, 'files')
|
|
433
433
|
|
|
434
434
|
try:
|
|
435
|
-
update_replicas_states(rse=rse, files=files, issuer=request.environ
|
|
435
|
+
update_replicas_states(rse=rse, files=files, issuer=request.environ['issuer'], vo=request.environ['vo'])
|
|
436
436
|
except AccessDenied as error:
|
|
437
437
|
return generate_http_error_flask(401, error)
|
|
438
438
|
|
|
@@ -486,8 +486,8 @@ class Replicas(ErrorHandlingMethodView):
|
|
|
486
486
|
delete_replicas(
|
|
487
487
|
rse=rse,
|
|
488
488
|
files=files,
|
|
489
|
-
issuer=request.environ
|
|
490
|
-
vo=request.environ
|
|
489
|
+
issuer=request.environ['issuer'],
|
|
490
|
+
vo=request.environ['vo'],
|
|
491
491
|
ignore_availability=param_get(parameters, 'ignore_availability', default=False),
|
|
492
492
|
)
|
|
493
493
|
except AccessDenied as error:
|
|
@@ -746,8 +746,8 @@ class ListReplicas(ErrorHandlingMethodView):
|
|
|
746
746
|
yield rfile
|
|
747
747
|
|
|
748
748
|
rfiles = _list_and_sort_replicas(request_id=request.environ.get('request_id'),
|
|
749
|
-
issuer=request.environ
|
|
750
|
-
vo=request.environ
|
|
749
|
+
issuer=request.environ['issuer'],
|
|
750
|
+
vo=request.environ['vo'])
|
|
751
751
|
if metalink:
|
|
752
752
|
policy_schema = config_get('policy', 'schema', raise_exception=False, default='generic')
|
|
753
753
|
response_generator = _generate_metalink_response(rfiles, policy_schema)
|
|
@@ -822,7 +822,7 @@ class ReplicasDIDs(ErrorHandlingMethodView):
|
|
|
822
822
|
for pfn in get_did_from_pfns(pfns, rse, vo=vo):
|
|
823
823
|
yield dumps(pfn) + '\n'
|
|
824
824
|
|
|
825
|
-
return try_stream(generate(vo=request.environ
|
|
825
|
+
return try_stream(generate(vo=request.environ['vo']))
|
|
826
826
|
except AccessDenied as error:
|
|
827
827
|
return generate_http_error_flask(401, error)
|
|
828
828
|
|
|
@@ -882,7 +882,7 @@ class BadReplicas(ErrorHandlingMethodView):
|
|
|
882
882
|
|
|
883
883
|
try:
|
|
884
884
|
not_declared_files = declare_bad_file_replicas(replicas, reason=reason,
|
|
885
|
-
issuer=request.environ
|
|
885
|
+
issuer=request.environ['issuer'], vo=request.environ['vo'],
|
|
886
886
|
force=force)
|
|
887
887
|
return not_declared_files, 201
|
|
888
888
|
except AccessDenied as error:
|
|
@@ -1002,7 +1002,7 @@ class SuspiciousReplicas(ErrorHandlingMethodView):
|
|
|
1002
1002
|
reason = param_get(parameters, 'reason', default=None)
|
|
1003
1003
|
|
|
1004
1004
|
try:
|
|
1005
|
-
not_declared_files = declare_suspicious_file_replicas(pfns=pfns, reason=reason, issuer=request.environ
|
|
1005
|
+
not_declared_files = declare_suspicious_file_replicas(pfns=pfns, reason=reason, issuer=request.environ['issuer'], vo=request.environ['vo'])
|
|
1006
1006
|
return not_declared_files, 201
|
|
1007
1007
|
except AccessDenied as error:
|
|
1008
1008
|
return generate_http_error_flask(401, error)
|
|
@@ -1081,7 +1081,7 @@ class SuspiciousReplicas(ErrorHandlingMethodView):
|
|
|
1081
1081
|
if 'nattempts' in params:
|
|
1082
1082
|
nattempts = int(params['nattempts'][0])
|
|
1083
1083
|
|
|
1084
|
-
result = get_suspicious_files(rse_expression=rse_expression, younger_than=younger_than, nattempts=nattempts, vo=request.environ
|
|
1084
|
+
result = get_suspicious_files(rse_expression=rse_expression, younger_than=younger_than, nattempts=nattempts, vo=request.environ['vo'])
|
|
1085
1085
|
return Response(render_json(result), 200, content_type='application/json')
|
|
1086
1086
|
|
|
1087
1087
|
|
|
@@ -1180,7 +1180,8 @@ class BadReplicasStates(ErrorHandlingMethodView):
|
|
|
1180
1180
|
406:
|
|
1181
1181
|
description: Not acceptable
|
|
1182
1182
|
"""
|
|
1183
|
-
|
|
1183
|
+
list_pfns = False
|
|
1184
|
+
state, rse, younger_than, older_than, limit = None, None, None, None, None
|
|
1184
1185
|
if request.query_string:
|
|
1185
1186
|
query_string = request.query_string.decode(encoding='utf-8')
|
|
1186
1187
|
try:
|
|
@@ -1208,7 +1209,7 @@ class BadReplicasStates(ErrorHandlingMethodView):
|
|
|
1208
1209
|
vo=vo):
|
|
1209
1210
|
yield dumps(row, cls=APIEncoder) + '\n'
|
|
1210
1211
|
|
|
1211
|
-
return try_stream(generate(vo=request.environ
|
|
1212
|
+
return try_stream(generate(vo=request.environ['vo']))
|
|
1212
1213
|
|
|
1213
1214
|
|
|
1214
1215
|
class BadReplicasSummary(ErrorHandlingMethodView):
|
|
@@ -1287,7 +1288,7 @@ class BadReplicasSummary(ErrorHandlingMethodView):
|
|
|
1287
1288
|
to_date=to_date, vo=vo):
|
|
1288
1289
|
yield dumps(row, cls=APIEncoder) + '\n'
|
|
1289
1290
|
|
|
1290
|
-
return try_stream(generate(vo=request.environ
|
|
1291
|
+
return try_stream(generate(vo=request.environ['vo']))
|
|
1291
1292
|
|
|
1292
1293
|
|
|
1293
1294
|
class DatasetReplicas(ErrorHandlingMethodView):
|
|
@@ -1370,7 +1371,7 @@ class DatasetReplicas(ErrorHandlingMethodView):
|
|
|
1370
1371
|
description: Not acceptable
|
|
1371
1372
|
"""
|
|
1372
1373
|
try:
|
|
1373
|
-
scope, name = parse_scope_name(scope_name, request.environ
|
|
1374
|
+
scope, name = parse_scope_name(scope_name, request.environ['vo'])
|
|
1374
1375
|
|
|
1375
1376
|
def generate(_deep, vo):
|
|
1376
1377
|
for row in list_dataset_replicas(scope=scope, name=name, deep=_deep, vo=vo):
|
|
@@ -1378,7 +1379,7 @@ class DatasetReplicas(ErrorHandlingMethodView):
|
|
|
1378
1379
|
|
|
1379
1380
|
deep = request.args.get('deep', default=False)
|
|
1380
1381
|
|
|
1381
|
-
return try_stream(generate(_deep=deep, vo=request.environ
|
|
1382
|
+
return try_stream(generate(_deep=deep, vo=request.environ['vo']))
|
|
1382
1383
|
except ValueError as error:
|
|
1383
1384
|
return generate_http_error_flask(400, error)
|
|
1384
1385
|
|
|
@@ -1483,7 +1484,7 @@ class DatasetReplicasBulk(ErrorHandlingMethodView):
|
|
|
1483
1484
|
for row in list_dataset_replicas_bulk(dids=dids, vo=vo):
|
|
1484
1485
|
yield dumps(row, cls=APIEncoder) + '\n'
|
|
1485
1486
|
|
|
1486
|
-
return try_stream(generate(vo=request.environ
|
|
1487
|
+
return try_stream(generate(vo=request.environ['vo']))
|
|
1487
1488
|
except InvalidObject as error:
|
|
1488
1489
|
return generate_http_error_flask(400, error, f'Cannot validate DIDs: {error}')
|
|
1489
1490
|
|
|
@@ -1520,7 +1521,7 @@ class DatasetReplicasVP(ErrorHandlingMethodView):
|
|
|
1520
1521
|
description: Not acceptable
|
|
1521
1522
|
"""
|
|
1522
1523
|
try:
|
|
1523
|
-
scope, name = parse_scope_name(scope_name, request.environ
|
|
1524
|
+
scope, name = parse_scope_name(scope_name, request.environ['vo'])
|
|
1524
1525
|
|
|
1525
1526
|
def generate(_deep, vo):
|
|
1526
1527
|
for row in list_dataset_replicas_vp(scope=scope, name=name, deep=_deep, vo=vo):
|
|
@@ -1528,7 +1529,7 @@ class DatasetReplicasVP(ErrorHandlingMethodView):
|
|
|
1528
1529
|
|
|
1529
1530
|
deep = request.args.get('deep', default=False)
|
|
1530
1531
|
|
|
1531
|
-
return try_stream(generate(_deep=deep, vo=request.environ
|
|
1532
|
+
return try_stream(generate(_deep=deep, vo=request.environ['vo']))
|
|
1532
1533
|
except ValueError as error:
|
|
1533
1534
|
return generate_http_error_flask(400, error)
|
|
1534
1535
|
|
|
@@ -1610,7 +1611,7 @@ class ReplicasRSE(ErrorHandlingMethodView):
|
|
|
1610
1611
|
for row in list_datasets_per_rse(rse=rse, vo=vo):
|
|
1611
1612
|
yield dumps(row, cls=APIEncoder) + '\n'
|
|
1612
1613
|
|
|
1613
|
-
return try_stream(generate(vo=request.environ
|
|
1614
|
+
return try_stream(generate(vo=request.environ['vo']))
|
|
1614
1615
|
|
|
1615
1616
|
|
|
1616
1617
|
class BadDIDs(ErrorHandlingMethodView):
|
|
@@ -1676,11 +1677,11 @@ class BadDIDs(ErrorHandlingMethodView):
|
|
|
1676
1677
|
not_declared_files = add_bad_dids(
|
|
1677
1678
|
dids=param_get(parameters, 'dids', default=[]),
|
|
1678
1679
|
rse=param_get(parameters, 'rse', default=None),
|
|
1679
|
-
issuer=request.environ
|
|
1680
|
+
issuer=request.environ['issuer'],
|
|
1680
1681
|
state=BadFilesStatus.BAD,
|
|
1681
1682
|
reason=param_get(parameters, 'reason', default=None),
|
|
1682
1683
|
expires_at=expires_at,
|
|
1683
|
-
vo=request.environ
|
|
1684
|
+
vo=request.environ['vo'],
|
|
1684
1685
|
)
|
|
1685
1686
|
except (ValueError, InvalidType) as error:
|
|
1686
1687
|
return generate_http_error_flask(400, ValueError.__name__, error.args[0])
|
|
@@ -1745,11 +1746,11 @@ class BadPFNs(ErrorHandlingMethodView):
|
|
|
1745
1746
|
try:
|
|
1746
1747
|
add_bad_pfns(
|
|
1747
1748
|
pfns=param_get(parameters, 'pfns', default=[]),
|
|
1748
|
-
issuer=request.environ
|
|
1749
|
+
issuer=request.environ['issuer'],
|
|
1749
1750
|
state=param_get(parameters, 'state', default=None),
|
|
1750
1751
|
reason=param_get(parameters, 'reason', default=None),
|
|
1751
1752
|
expires_at=expires_at,
|
|
1752
|
-
vo=request.environ
|
|
1753
|
+
vo=request.environ['vo'],
|
|
1753
1754
|
)
|
|
1754
1755
|
except (ValueError, InvalidType) as error:
|
|
1755
1756
|
return generate_http_error_flask(400, ValueError.__name__, error.args[0])
|
|
@@ -1816,8 +1817,8 @@ class Tombstone(ErrorHandlingMethodView):
|
|
|
1816
1817
|
rse=replica['rse'],
|
|
1817
1818
|
scope=replica['scope'],
|
|
1818
1819
|
name=replica['name'],
|
|
1819
|
-
issuer=request.environ
|
|
1820
|
-
vo=request.environ
|
|
1820
|
+
issuer=request.environ['issuer'],
|
|
1821
|
+
vo=request.environ['vo'],
|
|
1821
1822
|
)
|
|
1822
1823
|
except ReplicaNotFound as error:
|
|
1823
1824
|
return generate_http_error_flask(404, error)
|
|
@@ -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
|
|
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
|
|
191
|
-
vo=flask.request.environ
|
|
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
|
|
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
|
|
356
|
-
vo=flask.request.environ
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
964
|
-
vo=flask.request.environ
|
|
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
|