rucio 38.2.0__py3-none-any.whl → 38.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/bin_legacy/rucio.py +26 -23
- rucio/cli/command.py +36 -26
- rucio/cli/config.py +22 -7
- rucio/cli/did.py +2 -2
- rucio/cli/download.py +1 -1
- rucio/cli/opendata.py +78 -10
- rucio/cli/utils.py +13 -1
- rucio/client/accountclient.py +20 -19
- rucio/client/accountlimitclient.py +5 -4
- rucio/client/baseclient.py +25 -25
- rucio/client/configclient.py +29 -5
- rucio/client/credentialclient.py +2 -1
- rucio/client/didclient.py +33 -32
- rucio/client/diracclient.py +2 -1
- rucio/client/exportclient.py +2 -1
- rucio/client/importclient.py +2 -1
- rucio/client/lifetimeclient.py +3 -2
- rucio/client/lockclient.py +4 -3
- rucio/client/metaconventionsclient.py +5 -4
- rucio/client/opendataclient.py +8 -7
- rucio/client/pingclient.py +2 -1
- rucio/client/replicaclient.py +27 -26
- rucio/client/requestclient.py +8 -8
- rucio/client/richclient.py +6 -0
- rucio/client/rseclient.py +31 -28
- rucio/client/ruleclient.py +13 -12
- rucio/client/scopeclient.py +4 -3
- rucio/client/subscriptionclient.py +6 -5
- rucio/common/constants.py +23 -0
- rucio/common/exception.py +30 -0
- rucio/common/plugins.py +33 -15
- rucio/common/utils.py +3 -3
- rucio/core/config.py +8 -6
- rucio/core/credential.py +19 -26
- rucio/core/did.py +1 -1
- rucio/core/did_meta_plugins/did_column_meta.py +226 -69
- rucio/core/opendata.py +150 -8
- rucio/core/replica.py +3 -4
- rucio/core/request.py +1 -1
- rucio/core/rule.py +6 -3
- rucio/core/rule_grouping.py +5 -5
- rucio/gateway/account.py +8 -7
- rucio/gateway/config.py +2 -37
- rucio/gateway/opendata.py +2 -2
- rucio/gateway/request.py +2 -117
- rucio/gateway/rule.py +2 -2
- rucio/rse/protocols/webdav.py +5 -2
- rucio/rse/translation.py +3 -3
- rucio/transfertool/fts3.py +0 -19
- rucio/transfertool/fts3_plugins.py +3 -3
- rucio/vcsversion.py +3 -3
- rucio/web/rest/flaskapi/v1/accountlimits.py +4 -3
- rucio/web/rest/flaskapi/v1/accounts.py +26 -25
- rucio/web/rest/flaskapi/v1/archives.py +2 -2
- rucio/web/rest/flaskapi/v1/auth.py +15 -14
- rucio/web/rest/flaskapi/v1/common.py +4 -4
- rucio/web/rest/flaskapi/v1/config.py +57 -17
- rucio/web/rest/flaskapi/v1/credentials.py +3 -3
- rucio/web/rest/flaskapi/v1/dids.py +25 -24
- rucio/web/rest/flaskapi/v1/dirac.py +3 -2
- rucio/web/rest/flaskapi/v1/export.py +4 -2
- rucio/web/rest/flaskapi/v1/heartbeats.py +2 -1
- rucio/web/rest/flaskapi/v1/identities.py +5 -4
- rucio/web/rest/flaskapi/v1/import.py +3 -2
- rucio/web/rest/flaskapi/v1/lifetime_exceptions.py +3 -2
- rucio/web/rest/flaskapi/v1/locks.py +4 -3
- rucio/web/rest/flaskapi/v1/meta_conventions.py +4 -3
- rucio/web/rest/flaskapi/v1/metrics.py +2 -1
- rucio/web/rest/flaskapi/v1/nongrid_traces.py +2 -1
- rucio/web/rest/flaskapi/v1/opendata.py +7 -6
- rucio/web/rest/flaskapi/v1/opendata_public.py +6 -5
- rucio/web/rest/flaskapi/v1/ping.py +3 -2
- rucio/web/rest/flaskapi/v1/redirect.py +4 -3
- rucio/web/rest/flaskapi/v1/replicas.py +31 -31
- rucio/web/rest/flaskapi/v1/requests.py +7 -7
- rucio/web/rest/flaskapi/v1/rses.py +23 -16
- rucio/web/rest/flaskapi/v1/rules.py +9 -8
- rucio/web/rest/flaskapi/v1/scopes.py +4 -3
- rucio/web/rest/flaskapi/v1/subscriptions.py +9 -8
- rucio/web/rest/flaskapi/v1/traces.py +2 -1
- rucio/web/rest/flaskapi/v1/vos.py +4 -3
- {rucio-38.2.0.dist-info → rucio-38.4.0.dist-info}/METADATA +1 -1
- {rucio-38.2.0.dist-info → rucio-38.4.0.dist-info}/RECORD +142 -142
- {rucio-38.2.0.data → rucio-38.4.0.data}/data/rucio/etc/alembic.ini.template +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/data/rucio/etc/alembic_offline.ini.template +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/data/rucio/etc/globus-config.yml.template +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/data/rucio/etc/ldap.cfg.template +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/data/rucio/etc/mail_templates/rule_approval_request.tmpl +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/data/rucio/etc/mail_templates/rule_approved_admin.tmpl +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/data/rucio/etc/mail_templates/rule_approved_user.tmpl +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/data/rucio/etc/mail_templates/rule_denied_admin.tmpl +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/data/rucio/etc/mail_templates/rule_denied_user.tmpl +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/data/rucio/etc/mail_templates/rule_ok_notification.tmpl +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/data/rucio/etc/rse-accounts.cfg.template +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/data/rucio/etc/rucio.cfg.atlas.client.template +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/data/rucio/etc/rucio.cfg.template +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/data/rucio/etc/rucio_multi_vo.cfg.template +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/data/rucio/requirements.server.txt +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/data/rucio/tools/bootstrap.py +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/data/rucio/tools/merge_rucio_configs.py +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/data/rucio/tools/reset_database.py +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-abacus-account +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-abacus-collection-replica +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-abacus-rse +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-admin +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-atropos +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-auditor +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-automatix +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-bb8 +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-cache-client +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-cache-consumer +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-conveyor-finisher +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-conveyor-poller +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-conveyor-preparer +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-conveyor-receiver +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-conveyor-stager +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-conveyor-submitter +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-conveyor-throttler +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-dark-reaper +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-dumper +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-follower +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-hermes +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-judge-cleaner +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-judge-evaluator +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-judge-injector +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-judge-repairer +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-kronos +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-minos +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-minos-temporary-expiration +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-necromancer +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-oauth-manager +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-reaper +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-replica-recoverer +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-rse-decommissioner +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-storage-consistency-actions +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-transmogrifier +0 -0
- {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-undertaker +0 -0
- {rucio-38.2.0.dist-info → rucio-38.4.0.dist-info}/WHEEL +0 -0
- {rucio-38.2.0.dist-info → rucio-38.4.0.dist-info}/licenses/AUTHORS.rst +0 -0
- {rucio-38.2.0.dist-info → rucio-38.4.0.dist-info}/licenses/LICENSE +0 -0
- {rucio-38.2.0.dist-info → rucio-38.4.0.dist-info}/top_level.txt +0 -0
rucio/core/opendata.py
CHANGED
|
@@ -21,10 +21,14 @@ from sqlalchemy.exc import DataError, IntegrityError
|
|
|
21
21
|
from sqlalchemy.sql.expression import bindparam, select
|
|
22
22
|
|
|
23
23
|
from rucio.common import exception
|
|
24
|
+
from rucio.common.config import config_get, config_get_bool, config_get_int
|
|
25
|
+
from rucio.common.constants import DEFAULT_VO
|
|
24
26
|
from rucio.common.exception import OpenDataError, OpenDataInvalidStateUpdate
|
|
27
|
+
from rucio.common.types import InternalAccount
|
|
25
28
|
from rucio.core.did import list_files
|
|
26
29
|
from rucio.core.monitor import MetricManager
|
|
27
30
|
from rucio.core.replica import list_replicas
|
|
31
|
+
from rucio.core.rule import add_rule
|
|
28
32
|
from rucio.db.sqla import models
|
|
29
33
|
from rucio.db.sqla.constants import DIDType, OpenDataDIDState
|
|
30
34
|
|
|
@@ -300,6 +304,7 @@ def get_opendata_did(
|
|
|
300
304
|
include_files: bool = True,
|
|
301
305
|
include_metadata: bool = False,
|
|
302
306
|
include_doi: bool = True,
|
|
307
|
+
include_rule: bool = True,
|
|
303
308
|
session: "Session",
|
|
304
309
|
) -> dict[str, Any]:
|
|
305
310
|
"""
|
|
@@ -312,10 +317,11 @@ def get_opendata_did(
|
|
|
312
317
|
include_files: If True, include a list of associated files. Defaults to True.
|
|
313
318
|
include_metadata: If True, include extended metadata. Defaults to False.
|
|
314
319
|
include_doi: If True, include DOI (Digital Object Identifier) information. Defaults to True.
|
|
320
|
+
include_rule: If True, include the Opendata replication rule. Defaults to True.
|
|
315
321
|
session: SQLAlchemy session to use for the query.
|
|
316
322
|
|
|
317
323
|
Returns:
|
|
318
|
-
A dictionary containing
|
|
324
|
+
A dictionary containing info about the specified DID which include "scope", "name", "state", "meta" (if requested), etc.
|
|
319
325
|
"""
|
|
320
326
|
|
|
321
327
|
query = select(
|
|
@@ -345,6 +351,8 @@ def get_opendata_did(
|
|
|
345
351
|
result["doi"] = get_opendata_doi(scope=scope, name=name, session=session)
|
|
346
352
|
if include_metadata:
|
|
347
353
|
result["meta"] = get_opendata_meta(scope=scope, name=name, session=session)
|
|
354
|
+
if include_rule:
|
|
355
|
+
result["rule"] = _fetch_opendata_rule(scope=scope, name=name, session=session)
|
|
348
356
|
if include_files:
|
|
349
357
|
opendata_files = get_opendata_did_files(scope=scope, name=name, session=session)
|
|
350
358
|
result["files"] = opendata_files
|
|
@@ -508,7 +516,7 @@ def update_opendata_did(
|
|
|
508
516
|
meta: Optional[Union[dict, str]] = None,
|
|
509
517
|
doi: Optional[str] = None,
|
|
510
518
|
session: "Session",
|
|
511
|
-
) ->
|
|
519
|
+
) -> dict[str, Any]:
|
|
512
520
|
"""
|
|
513
521
|
Update an existing Opendata DID in the catalog.
|
|
514
522
|
|
|
@@ -520,6 +528,9 @@ def update_opendata_did(
|
|
|
520
528
|
doi: DOI to associate with the DID. Must be a valid DOI string (e.g., "10.1234/foo.bar").
|
|
521
529
|
session: SQLAlchemy session to use for the operation.
|
|
522
530
|
|
|
531
|
+
Returns:
|
|
532
|
+
A dictionary containing the scope and name of the DID and details of the updates performed. (e.g., new/old state, new/old DOI, etc.)
|
|
533
|
+
|
|
523
534
|
Raises:
|
|
524
535
|
InputValidationError: If none of 'state', 'meta', or 'doi' are provided, or if the provided data is invalid.
|
|
525
536
|
OpenDataDataIdentifierNotFound: If the Opendata DID does not exist.
|
|
@@ -533,14 +544,18 @@ def update_opendata_did(
|
|
|
533
544
|
if not _check_opendata_did_exists(scope=scope, name=name, session=session):
|
|
534
545
|
raise exception.OpenDataDataIdentifierNotFound(f"OpenData DID '{scope}:{name}' not found.")
|
|
535
546
|
|
|
547
|
+
result = {}
|
|
548
|
+
|
|
536
549
|
if state is not None:
|
|
537
|
-
update_opendata_state(scope=scope, name=name, state=state, session=session)
|
|
550
|
+
result |= update_opendata_state(scope=scope, name=name, state=state, session=session)
|
|
538
551
|
|
|
539
552
|
if meta is not None:
|
|
540
|
-
update_opendata_meta(scope=scope, name=name, meta=meta, session=session)
|
|
553
|
+
result |= update_opendata_meta(scope=scope, name=name, meta=meta, session=session)
|
|
541
554
|
|
|
542
555
|
if doi is not None:
|
|
543
|
-
update_opendata_doi(scope=scope, name=name, doi=doi, session=session)
|
|
556
|
+
result |= update_opendata_doi(scope=scope, name=name, doi=doi, session=session)
|
|
557
|
+
|
|
558
|
+
return result
|
|
544
559
|
|
|
545
560
|
|
|
546
561
|
def update_opendata_meta(
|
|
@@ -549,7 +564,7 @@ def update_opendata_meta(
|
|
|
549
564
|
name: str,
|
|
550
565
|
meta: Union[dict, str],
|
|
551
566
|
session: "Session",
|
|
552
|
-
) ->
|
|
567
|
+
) -> dict[str, Any]:
|
|
553
568
|
"""
|
|
554
569
|
Update the metadata associated with an Opendata DID.
|
|
555
570
|
|
|
@@ -559,6 +574,9 @@ def update_opendata_meta(
|
|
|
559
574
|
meta: Metadata to update for the DID. Must be a valid JSON object or string.
|
|
560
575
|
session: SQLAlchemy session to use for the operation.
|
|
561
576
|
|
|
577
|
+
Returns:
|
|
578
|
+
A dictionary containing the scope, name, and updated metadata of the Opendata DID.
|
|
579
|
+
|
|
562
580
|
Raises:
|
|
563
581
|
InputValidationError: If 'meta' is not a dictionary or a valid JSON string.
|
|
564
582
|
OpenDataDataIdentifierNotFound: If the Opendata DID does not exist.
|
|
@@ -598,6 +616,93 @@ def update_opendata_meta(
|
|
|
598
616
|
except DataError as error:
|
|
599
617
|
raise exception.InputValidationError(f"Invalid data: {error}")
|
|
600
618
|
|
|
619
|
+
return {"scope": scope, "name": name, "meta_new": meta}
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
def _fetch_opendata_rule(scope: "InternalScope",
|
|
623
|
+
name: str,
|
|
624
|
+
session: "Session"
|
|
625
|
+
) -> Optional[str]:
|
|
626
|
+
"""
|
|
627
|
+
Retrieves the replication rule ID associated with an Opendata DID, if it exists.
|
|
628
|
+
The rule is searched for in the rules table by matching the scope, name, account (root), rse_expression,
|
|
629
|
+
and copies (1) based on the configuration used for creating the rule.
|
|
630
|
+
|
|
631
|
+
Parameters:
|
|
632
|
+
scope: The scope under which the Opendata DID is registered.
|
|
633
|
+
name: The name of the Opendata DID.
|
|
634
|
+
session: SQLAlchemy session to use for the query.
|
|
635
|
+
Returns:
|
|
636
|
+
The replication rule ID if it exists, otherwise None.
|
|
637
|
+
"""
|
|
638
|
+
|
|
639
|
+
rule_rse_expression = config_get("opendata", "rule_rse_expression", raise_exception=False, default=None)
|
|
640
|
+
if not rule_rse_expression:
|
|
641
|
+
return None
|
|
642
|
+
|
|
643
|
+
rule_account = config_get("opendata", "rule_account", raise_exception=False, default="root")
|
|
644
|
+
rule_vo = config_get("opendata", "rule_vo", raise_exception=False, default=DEFAULT_VO)
|
|
645
|
+
rule_copies = config_get_int("opendata", "rule_copies", raise_exception=False, default=1)
|
|
646
|
+
|
|
647
|
+
return session.execute(
|
|
648
|
+
select(models.ReplicationRule.id).where(
|
|
649
|
+
and_(
|
|
650
|
+
models.ReplicationRule.scope == scope,
|
|
651
|
+
models.ReplicationRule.name == name,
|
|
652
|
+
models.ReplicationRule.account == InternalAccount(account=rule_account, vo=rule_vo),
|
|
653
|
+
models.ReplicationRule.rse_expression == rule_rse_expression,
|
|
654
|
+
models.ReplicationRule.copies == rule_copies,
|
|
655
|
+
)
|
|
656
|
+
)
|
|
657
|
+
).scalar()
|
|
658
|
+
|
|
659
|
+
|
|
660
|
+
def _add_opendata_rule(
|
|
661
|
+
scope: "InternalScope",
|
|
662
|
+
name: str,
|
|
663
|
+
session: "Session"
|
|
664
|
+
) -> str:
|
|
665
|
+
"""
|
|
666
|
+
Create a replication rule for an Opendata DID.
|
|
667
|
+
The rule is created with parameters defined in the configuration file under the [opendata] section.
|
|
668
|
+
|
|
669
|
+
Parameters:
|
|
670
|
+
scope: The scope under which the Opendata DID is registered.
|
|
671
|
+
name: The name of the Opendata DID.
|
|
672
|
+
session: SQLAlchemy session to use for the operation.
|
|
673
|
+
Returns:
|
|
674
|
+
The ID of the created replication rule.
|
|
675
|
+
Raises:
|
|
676
|
+
ValueError: If there is an error during the rule creation process.
|
|
677
|
+
"""
|
|
678
|
+
|
|
679
|
+
rule_asynchronous = config_get_bool("opendata", "rule_asynchronous", raise_exception=False, default=False)
|
|
680
|
+
rule_activity = config_get("opendata", "rule_activity", raise_exception=False, default=None)
|
|
681
|
+
rule_rse_expression = config_get("opendata", "rule_rse_expression", raise_exception=True)
|
|
682
|
+
rule_account = config_get("opendata", "rule_account", raise_exception=False, default="root")
|
|
683
|
+
rule_vo = config_get("opendata", "rule_vo", raise_exception=False, default=DEFAULT_VO)
|
|
684
|
+
rule_copies = config_get_int("opendata", "rule_copies", raise_exception=False, default=1)
|
|
685
|
+
|
|
686
|
+
add_rule_result = add_rule(
|
|
687
|
+
dids=[{"scope": scope, "name": name}],
|
|
688
|
+
# We need an account, perhaps we should pass the issuer argument around like in other methods with account
|
|
689
|
+
account=InternalAccount(account=rule_account, vo=rule_vo),
|
|
690
|
+
copies=rule_copies,
|
|
691
|
+
rse_expression=rule_rse_expression,
|
|
692
|
+
grouping="DATASET",
|
|
693
|
+
weight=None,
|
|
694
|
+
lifetime=None,
|
|
695
|
+
locked=False,
|
|
696
|
+
subscription_id=None,
|
|
697
|
+
activity=rule_activity,
|
|
698
|
+
asynchronous=rule_asynchronous,
|
|
699
|
+
session=session,
|
|
700
|
+
)
|
|
701
|
+
if len(add_rule_result) != 1:
|
|
702
|
+
raise ValueError(f"Error adding Open Data rule: {add_rule_result}")
|
|
703
|
+
|
|
704
|
+
return add_rule_result[0]
|
|
705
|
+
|
|
601
706
|
|
|
602
707
|
def update_opendata_state(
|
|
603
708
|
*,
|
|
@@ -605,9 +710,10 @@ def update_opendata_state(
|
|
|
605
710
|
name: str,
|
|
606
711
|
state: OpenDataDIDState,
|
|
607
712
|
session: "Session",
|
|
608
|
-
) ->
|
|
713
|
+
) -> dict[str, Any]:
|
|
609
714
|
"""
|
|
610
715
|
Update the state of an Opendata DID.
|
|
716
|
+
If the new state is PUBLIC, a replication rule may be created based on configuration.
|
|
611
717
|
|
|
612
718
|
Parameters:
|
|
613
719
|
scope: The scope under which the Opendata DID is registered.
|
|
@@ -615,6 +721,9 @@ def update_opendata_state(
|
|
|
615
721
|
state: The new state to set for the Opendata DID.
|
|
616
722
|
session: SQLAlchemy session to use for the operation.
|
|
617
723
|
|
|
724
|
+
Returns:
|
|
725
|
+
A dictionary with the scope and name of the DID and the rule id if a rule was created and the old and new state.
|
|
726
|
+
|
|
618
727
|
Raises:
|
|
619
728
|
InputValidationError: If the provided state is not a valid OpenDataDIDState.
|
|
620
729
|
OpenDataDataIdentifierNotFound: If the Opendata DID does not exist.
|
|
@@ -676,15 +785,30 @@ def update_opendata_state(
|
|
|
676
785
|
if state_before == OpenDataDIDState.DRAFT:
|
|
677
786
|
raise OpenDataInvalidStateUpdate("Cannot set state to SUSPENDED from DRAFT. First set it to PUBLIC.")
|
|
678
787
|
|
|
788
|
+
output = {"scope": scope, "name": name, "state_old": state_before, "state_new": state}
|
|
789
|
+
|
|
679
790
|
try:
|
|
680
791
|
result = session.execute(update_query)
|
|
681
792
|
|
|
682
793
|
if result.rowcount == 0:
|
|
683
794
|
raise ValueError(f"Error updating Opendata state for DID '{scope}:{name}'.")
|
|
684
795
|
|
|
796
|
+
if state == OpenDataDIDState.PUBLIC:
|
|
797
|
+
rule_enable = config_get_bool("opendata", "rule_enable", raise_exception=False, default=False)
|
|
798
|
+
if rule_enable:
|
|
799
|
+
rule_id = _fetch_opendata_rule(scope=scope, name=name, session=session)
|
|
800
|
+
if rule_id:
|
|
801
|
+
output["rule"] = rule_id
|
|
802
|
+
output["comments"] = "Replication rule already exists"
|
|
803
|
+
else:
|
|
804
|
+
output["rule"] = _add_opendata_rule(scope=scope, name=name, session=session)
|
|
805
|
+
output["comments"] = "Replication rule created"
|
|
806
|
+
|
|
685
807
|
except DataError as error:
|
|
686
808
|
raise exception.InputValidationError(f"Invalid data: {error}")
|
|
687
809
|
|
|
810
|
+
return output
|
|
811
|
+
|
|
688
812
|
|
|
689
813
|
def update_opendata_doi(
|
|
690
814
|
*,
|
|
@@ -692,7 +816,7 @@ def update_opendata_doi(
|
|
|
692
816
|
name: str,
|
|
693
817
|
doi: str,
|
|
694
818
|
session: "Session",
|
|
695
|
-
) ->
|
|
819
|
+
) -> dict[str, Any]:
|
|
696
820
|
"""
|
|
697
821
|
Update the DOI (Digital Object Identifier) associated with an Opendata DID.
|
|
698
822
|
|
|
@@ -702,6 +826,9 @@ def update_opendata_doi(
|
|
|
702
826
|
doi: The new DOI to associate with the Opendata DID. Must be a valid DOI string.
|
|
703
827
|
session: SQLAlchemy session to use for the operation.
|
|
704
828
|
|
|
829
|
+
Returns:
|
|
830
|
+
A dictionary containing the scope, name, new DOI, and previous DOI of the Opendata DID.
|
|
831
|
+
|
|
705
832
|
Raises:
|
|
706
833
|
InputValidationError: If the provided DOI is not a valid string or does not match the expected format.
|
|
707
834
|
OpenDataDataIdentifierNotFound: If the Opendata DID does not exist.
|
|
@@ -740,5 +867,20 @@ def update_opendata_doi(
|
|
|
740
867
|
if result.rowcount == 0:
|
|
741
868
|
raise ValueError(f"Error updating Opendata DOI for DID '{scope}:{name}'.")
|
|
742
869
|
|
|
870
|
+
except IntegrityError as error:
|
|
871
|
+
msg = str(error)
|
|
872
|
+
|
|
873
|
+
if (
|
|
874
|
+
search(r'ORA-00001: unique constraint \([^)]+\) violated', msg)
|
|
875
|
+
or search(r'UNIQUE constraint failed: dids_opendata_doi\.doi', msg)
|
|
876
|
+
or search(r'1062.*Duplicate entry.*for key', msg)
|
|
877
|
+
or search(r'duplicate key value violates unique constraint', msg)
|
|
878
|
+
or search(r'columns?.*not unique', msg)
|
|
879
|
+
):
|
|
880
|
+
raise exception.OpenDataDuplicateDOI(doi=doi)
|
|
881
|
+
|
|
882
|
+
raise exception.OpenDataError()
|
|
743
883
|
except DataError as error:
|
|
744
884
|
raise exception.InputValidationError(f"Invalid data: {error}")
|
|
885
|
+
|
|
886
|
+
return {"scope": scope, "name": name, "doi_new": doi, "doi_old": doi_before}
|
rucio/core/replica.py
CHANGED
|
@@ -22,7 +22,6 @@ from curses.ascii import isprint
|
|
|
22
22
|
from datetime import datetime, timedelta
|
|
23
23
|
from hashlib import sha256
|
|
24
24
|
from itertools import groupby
|
|
25
|
-
from json import dumps
|
|
26
25
|
from re import match
|
|
27
26
|
from struct import unpack
|
|
28
27
|
from traceback import format_exc
|
|
@@ -2304,9 +2303,9 @@ def __cleanup_after_replica_deletion(
|
|
|
2304
2303
|
for scope, name, did_type in session.execute(stmt):
|
|
2305
2304
|
if did_type == DIDType.DATASET:
|
|
2306
2305
|
messages.append({'event_type': 'ERASE',
|
|
2307
|
-
'payload':
|
|
2308
|
-
|
|
2309
|
-
|
|
2306
|
+
'payload': {'scope': scope.external,
|
|
2307
|
+
'name': name,
|
|
2308
|
+
'account': 'root'}})
|
|
2310
2309
|
dids_to_delete.add(ScopeName(scope=scope, name=name))
|
|
2311
2310
|
|
|
2312
2311
|
# Remove Archive Constituents
|
rucio/core/request.py
CHANGED
|
@@ -1513,7 +1513,7 @@ class TransferStatsManager:
|
|
|
1513
1513
|
def __enter__(self) -> "TransferStatsManager":
|
|
1514
1514
|
self.record_stats = config_get_bool('transfers', 'stats_enabled', default=self.record_stats)
|
|
1515
1515
|
downsample_period = config_get_int('transfers', 'stats_downsample_period', default=self.downsample_period)
|
|
1516
|
-
# Introduce some voluntary jitter to reduce the
|
|
1516
|
+
# Introduce some voluntary jitter to reduce the likelihood of performing this database
|
|
1517
1517
|
# operation multiple times in parallel.
|
|
1518
1518
|
self.downsample_period = random.randint(downsample_period * 3 // 4, math.ceil(downsample_period * 5 / 4)) # noqa: S311
|
|
1519
1519
|
if self.record_stats:
|
rucio/core/rule.py
CHANGED
|
@@ -37,7 +37,7 @@ import rucio.core.lock # import get_replica_locks, get_files_and_replica_locks_
|
|
|
37
37
|
import rucio.core.replica # import get_and_lock_file_replicas, get_and_lock_file_replicas_for_dataset
|
|
38
38
|
from rucio.common.cache import MemcacheRegion
|
|
39
39
|
from rucio.common.config import config_get
|
|
40
|
-
from rucio.common.constants import DEFAULT_VO, RseAttr
|
|
40
|
+
from rucio.common.constants import DEFAULT_ACTIVITY, DEFAULT_VO, POLICY_ALGORITHM_TYPES_LITERAL, RseAttr
|
|
41
41
|
from rucio.common.exception import (
|
|
42
42
|
DataIdentifierNotFound,
|
|
43
43
|
DuplicateRule,
|
|
@@ -98,7 +98,7 @@ class AutoApprove(PolicyPackageAlgorithms):
|
|
|
98
98
|
Handle automatic approval algorithms for replication rules
|
|
99
99
|
"""
|
|
100
100
|
|
|
101
|
-
_algorithm_type = 'auto_approve'
|
|
101
|
+
_algorithm_type: POLICY_ALGORITHM_TYPES_LITERAL = 'auto_approve'
|
|
102
102
|
|
|
103
103
|
def __init__(self, rule: models.ReplicationRule, did: models.DataIdentifier, session: 'Session', vo: str = DEFAULT_VO) -> None:
|
|
104
104
|
super().__init__()
|
|
@@ -180,7 +180,7 @@ def add_rule(
|
|
|
180
180
|
locked: bool,
|
|
181
181
|
subscription_id: Optional[str],
|
|
182
182
|
source_replica_expression: Optional[str] = None,
|
|
183
|
-
activity: str =
|
|
183
|
+
activity: Optional[str] = None,
|
|
184
184
|
notify: Optional[Literal['Y', 'N', 'C', 'P']] = None,
|
|
185
185
|
purge_replicas: bool = False,
|
|
186
186
|
ignore_availability: bool = False,
|
|
@@ -232,6 +232,9 @@ def add_rule(
|
|
|
232
232
|
if copies <= 0:
|
|
233
233
|
raise InvalidValueForKey("The number of copies for a replication rule should be greater than 0.")
|
|
234
234
|
|
|
235
|
+
if not activity:
|
|
236
|
+
activity = DEFAULT_ACTIVITY
|
|
237
|
+
|
|
235
238
|
rule_ids = []
|
|
236
239
|
|
|
237
240
|
grouping_value = {'ALL': RuleGrouping.ALL, 'NONE': RuleGrouping.NONE}.get(grouping, RuleGrouping.DATASET)
|
rucio/core/rule_grouping.py
CHANGED
|
@@ -690,7 +690,7 @@ def __repair_stuck_locks_with_none_grouping(datasetfiles, locks, replicas, sourc
|
|
|
690
690
|
associated_replica.lock_cnt = session.execute(stmt).scalar_one()
|
|
691
691
|
continue
|
|
692
692
|
# Check if this is a STUCK lock due to source_replica filtering
|
|
693
|
-
if source_rses:
|
|
693
|
+
if source_rses and not lock.repair_cnt:
|
|
694
694
|
associated_replica = [replica for replica in replicas[(file['scope'], file['name'])] if replica.rse_id == lock.rse_id][0]
|
|
695
695
|
# Check if there is an eligible source replica for this lock
|
|
696
696
|
if set(source_replicas.get((file['scope'], file['name']), [])).intersection(source_rses) and (selector_rse_dict.get(lock.rse_id, {}).get('availability_write', True) or rule.ignore_availability):
|
|
@@ -806,7 +806,7 @@ def __repair_stuck_locks_with_all_grouping(datasetfiles, locks, replicas, source
|
|
|
806
806
|
associated_replica.lock_cnt = session.execute(stmt).scalar_one()
|
|
807
807
|
continue
|
|
808
808
|
# Check if this is a STUCK lock due to source_replica filtering
|
|
809
|
-
if source_rses:
|
|
809
|
+
if source_rses and not lock.repair_cnt:
|
|
810
810
|
associated_replica = [replica for replica in replicas[(file['scope'], file['name'])] if replica.rse_id == lock.rse_id][0]
|
|
811
811
|
# Check if there is an eligible source replica for this lock
|
|
812
812
|
if set(source_replicas.get((file['scope'], file['name']), [])).intersection(source_rses) and (selector_rse_dict.get(lock.rse_id, {}).get('availability_write', True) or rule.ignore_availability):
|
|
@@ -891,7 +891,7 @@ def __repair_stuck_locks_with_dataset_grouping(datasetfiles, locks, replicas, so
|
|
|
891
891
|
associated_replica.lock_cnt = session.execute(stmt).scalar_one()
|
|
892
892
|
continue
|
|
893
893
|
# Check if this is a STUCK lock due to source_replica filtering
|
|
894
|
-
if source_rses:
|
|
894
|
+
if source_rses and not lock.repair_cnt:
|
|
895
895
|
associated_replica = [replica for replica in replicas[(file['scope'], file['name'])] if replica.rse_id == lock.rse_id][0]
|
|
896
896
|
# Check if there is an eligible source replica for this lock
|
|
897
897
|
if set(source_replicas.get((file['scope'], file['name']), [])).intersection(source_rses) and (selector_rse_dict.get(lock.rse_id, {}).get('availability_write', True) or rule.ignore_availability):
|
|
@@ -921,8 +921,8 @@ def __is_retry_required(lock, activity):
|
|
|
921
921
|
:param activity: The activity of the rule.
|
|
922
922
|
"""
|
|
923
923
|
|
|
924
|
-
created_at_diff = (datetime.utcnow() - lock.created_at).
|
|
925
|
-
updated_at_diff = (datetime.utcnow() - lock.updated_at).
|
|
924
|
+
created_at_diff = (datetime.utcnow() - lock.created_at).total_seconds()
|
|
925
|
+
updated_at_diff = (datetime.utcnow() - lock.updated_at).total_seconds()
|
|
926
926
|
|
|
927
927
|
if activity == 'Express':
|
|
928
928
|
if updated_at_diff > 3600 * 2:
|
rucio/gateway/account.py
CHANGED
|
@@ -14,10 +14,9 @@
|
|
|
14
14
|
|
|
15
15
|
from typing import TYPE_CHECKING, Any, Optional
|
|
16
16
|
|
|
17
|
-
import rucio.common.exception
|
|
18
|
-
import rucio.core.identity
|
|
19
17
|
import rucio.gateway.permission
|
|
20
18
|
from rucio.common.constants import DEFAULT_VO
|
|
19
|
+
from rucio.common.exception import AccessDenied, InvalidAccountType
|
|
21
20
|
from rucio.common.schema import validate_schema
|
|
22
21
|
from rucio.common.types import InternalAccount
|
|
23
22
|
from rucio.common.utils import gateway_update_return_dict
|
|
@@ -55,11 +54,13 @@ def add_account(
|
|
|
55
54
|
validate_schema(name='account', obj=account, vo=vo)
|
|
56
55
|
|
|
57
56
|
kwargs = {'account': account, 'type': type_}
|
|
57
|
+
if type_.upper() not in AccountType._member_names_:
|
|
58
|
+
raise InvalidAccountType(f"{type_} is an invalid account type. Choose from {AccountType._member_names_}")
|
|
58
59
|
|
|
59
60
|
with db_session(DatabaseOperationType.WRITE) as session:
|
|
60
61
|
auth_result = rucio.gateway.permission.has_permission(issuer=issuer, vo=vo, action='add_account', kwargs=kwargs, session=session)
|
|
61
62
|
if not auth_result.allowed:
|
|
62
|
-
raise
|
|
63
|
+
raise AccessDenied('Account %s can not add account. %s' % (issuer, auth_result.message))
|
|
63
64
|
|
|
64
65
|
internal_account = InternalAccount(account, vo=vo)
|
|
65
66
|
|
|
@@ -83,7 +84,7 @@ def del_account(
|
|
|
83
84
|
with db_session(DatabaseOperationType.WRITE) as session:
|
|
84
85
|
auth_result = rucio.gateway.permission.has_permission(issuer=issuer, vo=vo, action='del_account', kwargs=kwargs, session=session)
|
|
85
86
|
if not auth_result.allowed:
|
|
86
|
-
raise
|
|
87
|
+
raise AccessDenied('Account %s can not delete account. %s' % (issuer, auth_result.message))
|
|
87
88
|
|
|
88
89
|
internal_account = InternalAccount(account, vo=vo)
|
|
89
90
|
|
|
@@ -132,7 +133,7 @@ def update_account(
|
|
|
132
133
|
with db_session(DatabaseOperationType.WRITE) as session:
|
|
133
134
|
auth_result = rucio.gateway.permission.has_permission(issuer=issuer, vo=vo, action='update_account', kwargs=kwargs, session=session)
|
|
134
135
|
if not auth_result.allowed:
|
|
135
|
-
raise
|
|
136
|
+
raise AccessDenied('Account %s can not change %s of the account. %s' % (issuer, key, auth_result.message))
|
|
136
137
|
|
|
137
138
|
internal_account = InternalAccount(account, vo=vo)
|
|
138
139
|
|
|
@@ -242,7 +243,7 @@ def add_account_attribute(
|
|
|
242
243
|
with db_session(DatabaseOperationType.WRITE) as session:
|
|
243
244
|
auth_result = rucio.gateway.permission.has_permission(issuer=issuer, vo=vo, action='add_attribute', kwargs=kwargs, session=session)
|
|
244
245
|
if not auth_result.allowed:
|
|
245
|
-
raise
|
|
246
|
+
raise AccessDenied('Account %s can not add attributes. %s' % (issuer, auth_result.message))
|
|
246
247
|
|
|
247
248
|
internal_account = InternalAccount(account, vo=vo)
|
|
248
249
|
|
|
@@ -268,7 +269,7 @@ def del_account_attribute(
|
|
|
268
269
|
with db_session(DatabaseOperationType.WRITE) as session:
|
|
269
270
|
auth_result = rucio.gateway.permission.has_permission(issuer=issuer, vo=vo, action='del_attribute', kwargs=kwargs, session=session)
|
|
270
271
|
if not auth_result.allowed:
|
|
271
|
-
raise
|
|
272
|
+
raise AccessDenied('Account %s can not delete attribute. %s' % (issuer, auth_result.message))
|
|
272
273
|
|
|
273
274
|
internal_account = InternalAccount(account, vo=vo)
|
|
274
275
|
|
rucio/gateway/config.py
CHANGED
|
@@ -47,23 +47,6 @@ def sections(issuer: str, vo: str = DEFAULT_VO) -> list[str]:
|
|
|
47
47
|
return config.sections(session=session)
|
|
48
48
|
|
|
49
49
|
|
|
50
|
-
def add_section(section: str, issuer: str, vo: str = DEFAULT_VO) -> None:
|
|
51
|
-
"""
|
|
52
|
-
Add a section to the configuration.
|
|
53
|
-
|
|
54
|
-
:param section: The name of the section.
|
|
55
|
-
:param issuer: The issuer account.
|
|
56
|
-
:param vo: The VO to act on.
|
|
57
|
-
"""
|
|
58
|
-
|
|
59
|
-
kwargs = {'issuer': issuer, 'section': section}
|
|
60
|
-
with db_session(DatabaseOperationType.WRITE) as session:
|
|
61
|
-
auth_result = permission.has_permission(issuer=issuer, vo=vo, action='config_add_section', kwargs=kwargs, session=session)
|
|
62
|
-
if not auth_result.allowed:
|
|
63
|
-
raise exception.AccessDenied('%s cannot add section %s. %s' % (issuer, section, auth_result.message))
|
|
64
|
-
return config.add_section(section, session=session)
|
|
65
|
-
|
|
66
|
-
|
|
67
50
|
def has_section(section: str, issuer: str, vo: str = DEFAULT_VO) -> bool:
|
|
68
51
|
"""
|
|
69
52
|
Indicates whether the named section is present in the configuration.
|
|
@@ -79,25 +62,7 @@ def has_section(section: str, issuer: str, vo: str = DEFAULT_VO) -> bool:
|
|
|
79
62
|
auth_result = permission.has_permission(issuer=issuer, vo=vo, action='config_has_section', kwargs=kwargs, session=session)
|
|
80
63
|
if not auth_result.allowed:
|
|
81
64
|
raise exception.AccessDenied('%s cannot check existence of section %s. %s' % (issuer, section, auth_result.message))
|
|
82
|
-
return config.has_section(section, session=session)
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
def options(section: str, issuer: str, vo: str = DEFAULT_VO) -> list[str]:
|
|
86
|
-
"""
|
|
87
|
-
Returns a list of options available in the specified section.
|
|
88
|
-
|
|
89
|
-
:param section: The name of the section.
|
|
90
|
-
:param issuer: The issuer account.
|
|
91
|
-
:param vo: The VO to act on.
|
|
92
|
-
:returns: ['option', ...]
|
|
93
|
-
"""
|
|
94
|
-
|
|
95
|
-
kwargs = {'issuer': issuer, 'section': section}
|
|
96
|
-
with db_session(DatabaseOperationType.READ) as session:
|
|
97
|
-
auth_result = permission.has_permission(issuer=issuer, vo=vo, action='config_options', kwargs=kwargs, session=session)
|
|
98
|
-
if auth_result.allowed:
|
|
99
|
-
raise exception.AccessDenied('%s cannot retrieve options from section %s. %s' % (issuer, section, auth_result.message))
|
|
100
|
-
return config.options(section, session=session)
|
|
65
|
+
return config.has_section(section, session=session, use_cache=False)
|
|
101
66
|
|
|
102
67
|
|
|
103
68
|
def has_option(section: str, option: str, issuer: str, vo: str = DEFAULT_VO) -> bool:
|
|
@@ -116,7 +81,7 @@ def has_option(section: str, option: str, issuer: str, vo: str = DEFAULT_VO) ->
|
|
|
116
81
|
auth_result = permission.has_permission(issuer=issuer, vo=vo, action='config_has_option', kwargs=kwargs, session=session)
|
|
117
82
|
if not auth_result.allowed:
|
|
118
83
|
raise exception.AccessDenied('%s cannot check existence of option %s from section %s. %s' % (issuer, option, section, auth_result.message))
|
|
119
|
-
return config.has_option(section, option, session=session)
|
|
84
|
+
return config.has_option(section, option, session=session, use_cache=False)
|
|
120
85
|
|
|
121
86
|
|
|
122
87
|
def get(section: str, option: str, issuer: str, vo: str = DEFAULT_VO) -> Any:
|
rucio/gateway/opendata.py
CHANGED
|
@@ -151,7 +151,7 @@ def update_opendata_did(
|
|
|
151
151
|
meta: Optional[dict] = None,
|
|
152
152
|
doi: Optional[str] = None,
|
|
153
153
|
vo: str = DEFAULT_VO,
|
|
154
|
-
) ->
|
|
154
|
+
) -> dict[str, Any]:
|
|
155
155
|
"""
|
|
156
156
|
Update an existing Opendata DID in the Opendata catalog.
|
|
157
157
|
|
|
@@ -164,7 +164,7 @@ def update_opendata_did(
|
|
|
164
164
|
vo: The virtual organization.
|
|
165
165
|
|
|
166
166
|
Returns:
|
|
167
|
-
|
|
167
|
+
A dictionary containing the scope and name of the DID and details of the updates performed. (e.g., new/old state, new/old DOI, etc.)
|
|
168
168
|
|
|
169
169
|
Raises:
|
|
170
170
|
ValueError: If meta is a string and cannot be parsed as valid JSON.
|
rucio/gateway/request.py
CHANGED
|
@@ -20,7 +20,7 @@ from typing import TYPE_CHECKING, Any, Optional
|
|
|
20
20
|
|
|
21
21
|
from rucio.common import exception
|
|
22
22
|
from rucio.common.constants import DEFAULT_VO, TransferLimitDirection
|
|
23
|
-
from rucio.common.types import
|
|
23
|
+
from rucio.common.types import InternalScope
|
|
24
24
|
from rucio.common.utils import gateway_update_return_dict
|
|
25
25
|
from rucio.core import request
|
|
26
26
|
from rucio.core.rse import get_rse_id
|
|
@@ -31,122 +31,7 @@ from rucio.gateway import permission
|
|
|
31
31
|
if TYPE_CHECKING:
|
|
32
32
|
from collections.abc import Iterable, Iterator, Sequence
|
|
33
33
|
|
|
34
|
-
from rucio.db.sqla.constants import RequestState
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def queue_requests(
|
|
38
|
-
requests: "Iterable[RequestGatewayDict]",
|
|
39
|
-
issuer: str,
|
|
40
|
-
vo: str = DEFAULT_VO,
|
|
41
|
-
) -> list[dict[str, Any]]:
|
|
42
|
-
"""
|
|
43
|
-
Submit transfer or deletion requests on destination RSEs for data identifiers.
|
|
44
|
-
|
|
45
|
-
:param requests: List of dictionaries containing 'scope', 'name', 'dest_rse_id', 'request_type', 'attributes'
|
|
46
|
-
:param issuer: Issuing account as a string.
|
|
47
|
-
:param vo: The VO to act on.
|
|
48
|
-
:returns: List of Request-IDs as 32 character hex strings
|
|
49
|
-
"""
|
|
50
|
-
|
|
51
|
-
kwargs = {'requests': requests, 'issuer': issuer}
|
|
52
|
-
with db_session(DatabaseOperationType.WRITE) as session:
|
|
53
|
-
auth_result = permission.has_permission(issuer=issuer, vo=vo, action='queue_requests', kwargs=kwargs, session=session)
|
|
54
|
-
if not auth_result.allowed:
|
|
55
|
-
raise exception.AccessDenied(f'{issuer} can not queue request. {auth_result.message}')
|
|
56
|
-
|
|
57
|
-
for req in requests:
|
|
58
|
-
req['scope'] = InternalScope(req['scope'], vo=vo) # type: ignore (type reassignment)
|
|
59
|
-
if 'account' in req:
|
|
60
|
-
req['account'] = InternalAccount(req['account'], vo=vo) # type: ignore (type reassignment)
|
|
61
|
-
|
|
62
|
-
new_requests = request.queue_requests(requests, session=session)
|
|
63
|
-
return [gateway_update_return_dict(r, session=session) for r in new_requests]
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def cancel_request(
|
|
67
|
-
request_id: str,
|
|
68
|
-
issuer: str,
|
|
69
|
-
account: str,
|
|
70
|
-
vo: str = DEFAULT_VO,
|
|
71
|
-
) -> None:
|
|
72
|
-
"""
|
|
73
|
-
Cancel a request.
|
|
74
|
-
|
|
75
|
-
:param request_id: Request Identifier as a 32 character hex string.
|
|
76
|
-
:param issuer: Issuing account as a string.
|
|
77
|
-
:param account: Account identifier as a string.
|
|
78
|
-
:param vo: The VO to act on.
|
|
79
|
-
"""
|
|
80
|
-
|
|
81
|
-
kwargs = {'account': account, 'issuer': issuer, 'request_id': request_id}
|
|
82
|
-
with db_session(DatabaseOperationType.WRITE) as session:
|
|
83
|
-
auth_result = permission.has_permission(issuer=issuer, vo=vo, action='cancel_request_', kwargs=kwargs, session=session)
|
|
84
|
-
if not auth_result.allowed:
|
|
85
|
-
raise exception.AccessDenied('%s cannot cancel request %s. %s' % (account, request_id, auth_result.message))
|
|
86
|
-
|
|
87
|
-
raise NotImplementedError
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
def cancel_request_did(
|
|
91
|
-
scope: str,
|
|
92
|
-
name: str,
|
|
93
|
-
dest_rse: str,
|
|
94
|
-
request_type: str,
|
|
95
|
-
issuer: str,
|
|
96
|
-
account: str,
|
|
97
|
-
vo: str = DEFAULT_VO,
|
|
98
|
-
) -> dict[str, Any]:
|
|
99
|
-
"""
|
|
100
|
-
Cancel a request based on a DID and request type.
|
|
101
|
-
|
|
102
|
-
:param scope: Data identifier scope as a string.
|
|
103
|
-
:param name: Data identifier name as a string.
|
|
104
|
-
:param dest_rse: RSE name as a string.
|
|
105
|
-
:param request_type: Type of the request as a string.
|
|
106
|
-
:param issuer: Issuing account as a string.
|
|
107
|
-
:param account: Account identifier as a string.
|
|
108
|
-
:param vo: The VO to act on.
|
|
109
|
-
"""
|
|
110
|
-
|
|
111
|
-
with db_session(DatabaseOperationType.WRITE) as session:
|
|
112
|
-
dest_rse_id = get_rse_id(rse=dest_rse, vo=vo, session=session)
|
|
113
|
-
|
|
114
|
-
kwargs = {'account': account, 'issuer': issuer}
|
|
115
|
-
auth_result = permission.has_permission(issuer=issuer, vo=vo, action='cancel_request_did', kwargs=kwargs, session=session)
|
|
116
|
-
if not auth_result.allowed:
|
|
117
|
-
raise exception.AccessDenied(f'{account} cannot cancel {request_type} request for {scope}:{name}. {auth_result.message}')
|
|
118
|
-
|
|
119
|
-
internal_scope = InternalScope(scope, vo=vo)
|
|
120
|
-
return request.cancel_request_did(internal_scope, name, dest_rse_id, request_type, session=session)
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
def get_next(
|
|
124
|
-
request_type: "RequestType",
|
|
125
|
-
state: "RequestState",
|
|
126
|
-
issuer: str,
|
|
127
|
-
account: str,
|
|
128
|
-
vo: str = DEFAULT_VO,
|
|
129
|
-
) -> list[dict[str, Any]]:
|
|
130
|
-
"""
|
|
131
|
-
Retrieve the next request matching the request type and state.
|
|
132
|
-
|
|
133
|
-
:param request_type: Type of the request as a string.
|
|
134
|
-
:param state: State of the request as a string.
|
|
135
|
-
:param issuer: Issuing account as a string.
|
|
136
|
-
:param account: Account identifier as a string.
|
|
137
|
-
:param vo: The VO to act on.
|
|
138
|
-
:returns: Request as a dictionary.
|
|
139
|
-
"""
|
|
140
|
-
|
|
141
|
-
kwargs = {'account': account, 'issuer': issuer, 'request_type': request_type, 'state': state}
|
|
142
|
-
|
|
143
|
-
with db_session(DatabaseOperationType.WRITE) as session:
|
|
144
|
-
auth_result = permission.has_permission(issuer=issuer, vo=vo, action='get_next', kwargs=kwargs, session=session)
|
|
145
|
-
if not auth_result.allowed:
|
|
146
|
-
raise exception.AccessDenied(f'{account} cannot get the next request of type {request_type} in state {state}. {auth_result.message}')
|
|
147
|
-
|
|
148
|
-
reqs = request.get_and_mark_next(request_type, state, session=session)
|
|
149
|
-
return [gateway_update_return_dict(r, session=session) for r in reqs]
|
|
34
|
+
from rucio.db.sqla.constants import RequestState
|
|
150
35
|
|
|
151
36
|
|
|
152
37
|
def get_request_by_did(
|