rucio 35.7.0__py3-none-any.whl → 37.0.0rc2__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/alembicrevision.py +1 -1
- rucio/{daemons/c3po/collectors → cli}/__init__.py +1 -0
- rucio/cli/account.py +216 -0
- rucio-35.7.0.data/scripts/rucio → rucio/cli/bin_legacy/rucio.py +769 -486
- rucio-35.7.0.data/scripts/rucio-admin → rucio/cli/bin_legacy/rucio_admin.py +476 -423
- rucio/cli/command.py +272 -0
- rucio/cli/config.py +72 -0
- rucio/cli/did.py +191 -0
- rucio/cli/download.py +128 -0
- rucio/cli/lifetime_exception.py +33 -0
- rucio/cli/replica.py +162 -0
- rucio/cli/rse.py +293 -0
- rucio/cli/rule.py +158 -0
- rucio/cli/scope.py +40 -0
- rucio/cli/subscription.py +73 -0
- rucio/cli/upload.py +60 -0
- rucio/cli/utils.py +226 -0
- rucio/client/accountclient.py +0 -1
- rucio/client/baseclient.py +33 -24
- rucio/client/client.py +45 -1
- rucio/client/didclient.py +5 -3
- rucio/client/downloadclient.py +6 -8
- rucio/client/replicaclient.py +0 -2
- rucio/client/richclient.py +317 -0
- rucio/client/rseclient.py +4 -4
- rucio/client/uploadclient.py +26 -12
- rucio/common/bittorrent.py +234 -0
- rucio/common/cache.py +66 -29
- rucio/common/checksum.py +168 -0
- rucio/common/client.py +122 -0
- rucio/common/config.py +22 -35
- rucio/common/constants.py +61 -3
- rucio/common/didtype.py +72 -24
- rucio/common/dumper/__init__.py +45 -38
- rucio/common/dumper/consistency.py +75 -30
- rucio/common/dumper/data_models.py +63 -19
- rucio/common/dumper/path_parsing.py +19 -8
- rucio/common/exception.py +65 -8
- rucio/common/extra.py +5 -10
- rucio/common/logging.py +13 -13
- rucio/common/pcache.py +8 -7
- rucio/common/plugins.py +59 -27
- rucio/common/policy.py +12 -3
- rucio/common/schema/__init__.py +84 -34
- rucio/common/schema/generic.py +0 -17
- rucio/common/schema/generic_multi_vo.py +0 -17
- rucio/common/stomp_utils.py +383 -119
- rucio/common/test_rucio_server.py +12 -6
- rucio/common/types.py +132 -52
- rucio/common/utils.py +93 -643
- rucio/core/account_limit.py +14 -12
- rucio/core/authentication.py +2 -2
- rucio/core/config.py +23 -42
- rucio/core/credential.py +14 -15
- rucio/core/did.py +5 -1
- rucio/core/did_meta_plugins/elasticsearch_meta.py +407 -0
- rucio/core/did_meta_plugins/filter_engine.py +62 -3
- rucio/core/did_meta_plugins/json_meta.py +2 -2
- rucio/core/did_meta_plugins/mongo_meta.py +43 -30
- rucio/core/did_meta_plugins/postgres_meta.py +75 -39
- rucio/core/identity.py +6 -5
- rucio/core/importer.py +4 -3
- rucio/core/lifetime_exception.py +2 -2
- rucio/core/lock.py +8 -7
- rucio/core/message.py +6 -0
- rucio/core/monitor.py +30 -29
- rucio/core/naming_convention.py +2 -2
- rucio/core/nongrid_trace.py +2 -2
- rucio/core/oidc.py +11 -9
- rucio/core/permission/__init__.py +79 -37
- rucio/core/permission/generic.py +1 -7
- rucio/core/permission/generic_multi_vo.py +1 -7
- rucio/core/quarantined_replica.py +4 -3
- rucio/core/replica.py +464 -139
- rucio/core/replica_sorter.py +55 -59
- rucio/core/request.py +34 -32
- rucio/core/rse.py +301 -97
- rucio/core/rse_counter.py +1 -2
- rucio/core/rse_expression_parser.py +7 -7
- rucio/core/rse_selector.py +9 -7
- rucio/core/rule.py +41 -40
- rucio/core/rule_grouping.py +42 -40
- rucio/core/scope.py +5 -4
- rucio/core/subscription.py +26 -28
- rucio/core/topology.py +11 -11
- rucio/core/trace.py +2 -2
- rucio/core/transfer.py +29 -15
- rucio/core/volatile_replica.py +4 -3
- rucio/daemons/atropos/atropos.py +1 -1
- rucio/daemons/auditor/__init__.py +2 -2
- rucio/daemons/auditor/srmdumps.py +6 -6
- rucio/daemons/automatix/automatix.py +32 -21
- rucio/daemons/badreplicas/necromancer.py +2 -2
- rucio/daemons/bb8/nuclei_background_rebalance.py +1 -1
- rucio/daemons/bb8/t2_background_rebalance.py +1 -1
- rucio/daemons/cache/consumer.py +26 -90
- rucio/daemons/common.py +15 -25
- rucio/daemons/conveyor/finisher.py +2 -2
- rucio/daemons/conveyor/poller.py +18 -28
- rucio/daemons/conveyor/receiver.py +53 -123
- rucio/daemons/conveyor/stager.py +1 -0
- rucio/daemons/conveyor/submitter.py +3 -3
- rucio/daemons/hermes/hermes.py +129 -369
- rucio/daemons/judge/evaluator.py +2 -2
- rucio/daemons/oauthmanager/oauthmanager.py +3 -3
- rucio/daemons/reaper/dark_reaper.py +7 -3
- rucio/daemons/reaper/reaper.py +12 -16
- rucio/daemons/rsedecommissioner/config.py +1 -1
- rucio/daemons/rsedecommissioner/profiles/generic.py +5 -4
- rucio/daemons/rsedecommissioner/profiles/types.py +7 -6
- rucio/daemons/rsedecommissioner/rse_decommissioner.py +1 -1
- rucio/daemons/storage/consistency/actions.py +8 -6
- rucio/daemons/tracer/kronos.py +117 -142
- rucio/db/sqla/constants.py +5 -0
- rucio/db/sqla/migrate_repo/versions/1677d4d803c8_split_rse_availability_into_multiple.py +4 -4
- rucio/db/sqla/migrate_repo/versions/30d5206e9cad_increase_oauthrequest_redirect_msg_.py +37 -0
- rucio/db/sqla/models.py +157 -154
- rucio/db/sqla/session.py +58 -27
- rucio/db/sqla/types.py +2 -2
- rucio/db/sqla/util.py +2 -2
- rucio/gateway/account.py +18 -12
- rucio/gateway/account_limit.py +137 -60
- rucio/gateway/authentication.py +18 -12
- rucio/gateway/config.py +30 -20
- rucio/gateway/credential.py +9 -10
- rucio/gateway/did.py +70 -53
- rucio/gateway/dirac.py +6 -4
- rucio/gateway/exporter.py +3 -2
- rucio/gateway/heartbeat.py +6 -4
- rucio/gateway/identity.py +36 -51
- rucio/gateway/importer.py +3 -2
- rucio/gateway/lifetime_exception.py +3 -2
- rucio/gateway/meta_conventions.py +17 -6
- rucio/gateway/permission.py +4 -1
- rucio/gateway/quarantined_replica.py +3 -2
- rucio/gateway/replica.py +31 -22
- rucio/gateway/request.py +27 -18
- rucio/gateway/rse.py +69 -37
- rucio/gateway/rule.py +46 -26
- rucio/gateway/scope.py +3 -2
- rucio/gateway/subscription.py +14 -11
- rucio/gateway/vo.py +12 -8
- rucio/rse/__init__.py +3 -3
- rucio/rse/protocols/bittorrent.py +11 -1
- rucio/rse/protocols/cache.py +0 -11
- rucio/rse/protocols/dummy.py +0 -11
- rucio/rse/protocols/gfal.py +14 -9
- rucio/rse/protocols/globus.py +1 -1
- rucio/rse/protocols/http_cache.py +1 -1
- rucio/rse/protocols/posix.py +2 -2
- rucio/rse/protocols/protocol.py +84 -317
- rucio/rse/protocols/rclone.py +2 -1
- rucio/rse/protocols/rfio.py +10 -1
- rucio/rse/protocols/ssh.py +2 -1
- rucio/rse/protocols/storm.py +2 -13
- rucio/rse/protocols/webdav.py +74 -30
- rucio/rse/protocols/xrootd.py +2 -1
- rucio/rse/rsemanager.py +170 -53
- rucio/rse/translation.py +260 -0
- rucio/tests/common.py +23 -13
- rucio/tests/common_server.py +26 -9
- rucio/transfertool/bittorrent.py +15 -14
- rucio/transfertool/bittorrent_driver.py +5 -7
- rucio/transfertool/bittorrent_driver_qbittorrent.py +9 -8
- rucio/transfertool/fts3.py +20 -16
- rucio/transfertool/mock.py +2 -3
- rucio/vcsversion.py +4 -4
- rucio/version.py +7 -0
- rucio/web/rest/flaskapi/v1/accounts.py +17 -3
- rucio/web/rest/flaskapi/v1/auth.py +5 -5
- rucio/web/rest/flaskapi/v1/credentials.py +3 -2
- rucio/web/rest/flaskapi/v1/dids.py +21 -15
- rucio/web/rest/flaskapi/v1/identities.py +33 -9
- rucio/web/rest/flaskapi/v1/redirect.py +5 -4
- rucio/web/rest/flaskapi/v1/replicas.py +12 -8
- rucio/web/rest/flaskapi/v1/rses.py +15 -4
- rucio/web/rest/flaskapi/v1/traces.py +56 -19
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/data/rucio/etc/alembic.ini.template +1 -1
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/data/rucio/etc/alembic_offline.ini.template +1 -1
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/data/rucio/etc/rucio.cfg.atlas.client.template +3 -2
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/data/rucio/etc/rucio.cfg.template +3 -19
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/data/rucio/etc/rucio_multi_vo.cfg.template +1 -18
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/data/rucio/requirements.server.txt +97 -68
- rucio-37.0.0rc2.data/scripts/rucio +133 -0
- rucio-37.0.0rc2.data/scripts/rucio-admin +97 -0
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-atropos +2 -2
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-auditor +2 -1
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-automatix +2 -2
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-cache-client +17 -10
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-conveyor-receiver +1 -0
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-kronos +1 -0
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-minos +2 -2
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-minos-temporary-expiration +2 -2
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-necromancer +2 -2
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-reaper +6 -6
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-transmogrifier +2 -2
- rucio-37.0.0rc2.dist-info/METADATA +92 -0
- {rucio-35.7.0.dist-info → rucio-37.0.0rc2.dist-info}/RECORD +239 -245
- {rucio-35.7.0.dist-info → rucio-37.0.0rc2.dist-info}/licenses/AUTHORS.rst +3 -0
- rucio/common/schema/atlas.py +0 -413
- rucio/common/schema/belleii.py +0 -408
- rucio/common/schema/domatpc.py +0 -401
- rucio/common/schema/escape.py +0 -426
- rucio/common/schema/icecube.py +0 -406
- rucio/core/permission/atlas.py +0 -1348
- rucio/core/permission/belleii.py +0 -1077
- rucio/core/permission/escape.py +0 -1078
- rucio/daemons/c3po/algorithms/__init__.py +0 -13
- rucio/daemons/c3po/algorithms/simple.py +0 -134
- rucio/daemons/c3po/algorithms/t2_free_space.py +0 -128
- rucio/daemons/c3po/algorithms/t2_free_space_only_pop.py +0 -130
- rucio/daemons/c3po/algorithms/t2_free_space_only_pop_with_network.py +0 -294
- rucio/daemons/c3po/c3po.py +0 -371
- rucio/daemons/c3po/collectors/agis.py +0 -108
- rucio/daemons/c3po/collectors/free_space.py +0 -81
- rucio/daemons/c3po/collectors/jedi_did.py +0 -57
- rucio/daemons/c3po/collectors/mock_did.py +0 -51
- rucio/daemons/c3po/collectors/network_metrics.py +0 -71
- rucio/daemons/c3po/collectors/workload.py +0 -112
- rucio/daemons/c3po/utils/__init__.py +0 -13
- rucio/daemons/c3po/utils/dataset_cache.py +0 -50
- rucio/daemons/c3po/utils/expiring_dataset_cache.py +0 -56
- rucio/daemons/c3po/utils/expiring_list.py +0 -62
- rucio/daemons/c3po/utils/popularity.py +0 -85
- rucio/daemons/c3po/utils/timeseries.py +0 -89
- rucio/rse/protocols/gsiftp.py +0 -92
- rucio-35.7.0.data/scripts/rucio-c3po +0 -85
- rucio-35.7.0.dist-info/METADATA +0 -72
- /rucio/{daemons/c3po → cli/bin_legacy}/__init__.py +0 -0
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/data/rucio/etc/globus-config.yml.template +0 -0
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/data/rucio/etc/ldap.cfg.template +0 -0
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/data/rucio/etc/mail_templates/rule_approval_request.tmpl +0 -0
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/data/rucio/etc/mail_templates/rule_approved_admin.tmpl +0 -0
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/data/rucio/etc/mail_templates/rule_approved_user.tmpl +0 -0
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/data/rucio/etc/mail_templates/rule_denied_admin.tmpl +0 -0
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/data/rucio/etc/mail_templates/rule_denied_user.tmpl +0 -0
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/data/rucio/etc/mail_templates/rule_ok_notification.tmpl +0 -0
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/data/rucio/etc/rse-accounts.cfg.template +0 -0
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/data/rucio/tools/bootstrap.py +0 -0
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/data/rucio/tools/merge_rucio_configs.py +0 -0
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/data/rucio/tools/reset_database.py +0 -0
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-abacus-account +0 -0
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-abacus-collection-replica +0 -0
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-abacus-rse +0 -0
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-bb8 +0 -0
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-cache-consumer +0 -0
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-conveyor-finisher +0 -0
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-conveyor-poller +0 -0
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-conveyor-preparer +0 -0
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-conveyor-stager +0 -0
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-conveyor-submitter +0 -0
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-conveyor-throttler +0 -0
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-dark-reaper +0 -0
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-dumper +0 -0
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-follower +0 -0
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-hermes +0 -0
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-judge-cleaner +0 -0
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-judge-evaluator +0 -0
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-judge-injector +0 -0
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-judge-repairer +0 -0
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-oauth-manager +0 -0
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-replica-recoverer +0 -0
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-rse-decommissioner +0 -0
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-storage-consistency-actions +0 -0
- {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-undertaker +0 -0
- {rucio-35.7.0.dist-info → rucio-37.0.0rc2.dist-info}/WHEEL +0 -0
- {rucio-35.7.0.dist-info → rucio-37.0.0rc2.dist-info}/licenses/LICENSE +0 -0
- {rucio-35.7.0.dist-info → rucio-37.0.0rc2.dist-info}/top_level.txt +0 -0
rucio/common/config.py
CHANGED
|
@@ -17,18 +17,23 @@
|
|
|
17
17
|
import configparser
|
|
18
18
|
import json
|
|
19
19
|
import os
|
|
20
|
-
from
|
|
20
|
+
from functools import cache
|
|
21
21
|
from typing import TYPE_CHECKING, Any, Optional, TypeVar, Union, overload
|
|
22
22
|
|
|
23
23
|
from rucio.common import exception
|
|
24
|
-
from rucio.common.exception import ConfigNotFound, DatabaseException
|
|
24
|
+
from rucio.common.exception import ConfigLoadingError, ConfigNotFound, DatabaseException
|
|
25
25
|
|
|
26
26
|
_T = TypeVar('_T')
|
|
27
27
|
_U = TypeVar('_U')
|
|
28
28
|
|
|
29
29
|
if TYPE_CHECKING:
|
|
30
|
+
from collections.abc import Callable
|
|
31
|
+
|
|
30
32
|
from sqlalchemy.orm import Session
|
|
31
33
|
|
|
34
|
+
LEGACY_SECTION_NAME = {}
|
|
35
|
+
LEGACY_OPTION_NAME = {}
|
|
36
|
+
|
|
32
37
|
|
|
33
38
|
def convert_to_any_type(value: str) -> Union[bool, int, float, str]:
|
|
34
39
|
if value.lower() in ['true', 'yes', 'on']:
|
|
@@ -110,7 +115,7 @@ def config_get(
|
|
|
110
115
|
session: "Optional[Session]" = ...,
|
|
111
116
|
use_cache: bool = ...,
|
|
112
117
|
expiration_time: int = ...,
|
|
113
|
-
convert_type_fnc: Callable[[str], _T],
|
|
118
|
+
convert_type_fnc: 'Callable[[str], _T]',
|
|
114
119
|
) -> _T:
|
|
115
120
|
...
|
|
116
121
|
|
|
@@ -126,7 +131,7 @@ def config_get(
|
|
|
126
131
|
session: "Optional[Session]" = ...,
|
|
127
132
|
use_cache: bool = ...,
|
|
128
133
|
expiration_time: int = ...,
|
|
129
|
-
convert_type_fnc: Callable[[str], _U],
|
|
134
|
+
convert_type_fnc: 'Callable[[str], _U]',
|
|
130
135
|
) -> Union[_T, _U]:
|
|
131
136
|
...
|
|
132
137
|
|
|
@@ -143,7 +148,7 @@ def config_get(
|
|
|
143
148
|
session: "Optional[Session]" = ...,
|
|
144
149
|
use_cache: bool = ...,
|
|
145
150
|
expiration_time: int = ...,
|
|
146
|
-
convert_type_fnc: Callable[[str], _U],
|
|
151
|
+
convert_type_fnc: 'Callable[[str], _U]',
|
|
147
152
|
) -> Union[_T, _U]:
|
|
148
153
|
...
|
|
149
154
|
|
|
@@ -158,7 +163,7 @@ def config_get(
|
|
|
158
163
|
session: "Optional[Session]" = None,
|
|
159
164
|
use_cache: bool = True,
|
|
160
165
|
expiration_time: int = 900,
|
|
161
|
-
convert_type_fnc: Callable[[str], _T] = lambda x: x,
|
|
166
|
+
convert_type_fnc: 'Callable[[str], _T]' = lambda x: x,
|
|
162
167
|
) -> Union[_T, _U]:
|
|
163
168
|
"""
|
|
164
169
|
Return the string value for a given option in a section
|
|
@@ -196,7 +201,7 @@ def config_get(
|
|
|
196
201
|
except ConfigNotFound:
|
|
197
202
|
pass
|
|
198
203
|
|
|
199
|
-
from rucio.common.
|
|
204
|
+
from rucio.common.client import is_client
|
|
200
205
|
client_mode = is_client()
|
|
201
206
|
|
|
202
207
|
if not client_mode and check_config_table:
|
|
@@ -223,8 +228,6 @@ def get_legacy_config(section: str, option: str):
|
|
|
223
228
|
:param option: The option of the new config.
|
|
224
229
|
:returns: The string value of the legacy option if one is found, None otherwise.
|
|
225
230
|
"""
|
|
226
|
-
LEGACY_SECTION_NAME = {}
|
|
227
|
-
LEGACY_OPTION_NAME = {}
|
|
228
231
|
|
|
229
232
|
section = LEGACY_SECTION_NAME.get(section, section)
|
|
230
233
|
option = LEGACY_OPTION_NAME.get(option, option)
|
|
@@ -544,12 +547,12 @@ def config_get_list(
|
|
|
544
547
|
section: str,
|
|
545
548
|
option: str,
|
|
546
549
|
*,
|
|
547
|
-
default:
|
|
550
|
+
default: _T = ...,
|
|
548
551
|
check_config_table: bool = ...,
|
|
549
552
|
session: "Optional[Session]" = ...,
|
|
550
553
|
use_cache: bool = ...,
|
|
551
554
|
expiration_time: int = ...,
|
|
552
|
-
) -> list[str]:
|
|
555
|
+
) -> Union[list[str], _T]:
|
|
553
556
|
...
|
|
554
557
|
|
|
555
558
|
|
|
@@ -637,7 +640,7 @@ def __config_get_table(
|
|
|
637
640
|
session: "Optional[Session]" = None,
|
|
638
641
|
use_cache: bool = True,
|
|
639
642
|
expiration_time: int = 900,
|
|
640
|
-
convert_type_fnc: Optional[Callable[[str], _T]],
|
|
643
|
+
convert_type_fnc: Optional['Callable[[str], _T]'],
|
|
641
644
|
) -> _T:
|
|
642
645
|
"""
|
|
643
646
|
Search for a section-option configuration parameter in the configuration table
|
|
@@ -655,7 +658,6 @@ def __config_get_table(
|
|
|
655
658
|
:raises ConfigNotFound
|
|
656
659
|
:raises DatabaseException
|
|
657
660
|
"""
|
|
658
|
-
global __CONFIG
|
|
659
661
|
try:
|
|
660
662
|
from rucio.core.config import get as core_config_get
|
|
661
663
|
return core_config_get(section, option, default=default, session=session, use_cache=use_cache,
|
|
@@ -664,7 +666,7 @@ def __config_get_table(
|
|
|
664
666
|
if raise_exception and default is None:
|
|
665
667
|
raise err
|
|
666
668
|
if clean_cached:
|
|
667
|
-
|
|
669
|
+
clean_cached_config()
|
|
668
670
|
return default
|
|
669
671
|
|
|
670
672
|
|
|
@@ -714,14 +716,8 @@ def get_config_dirs() -> list[str]:
|
|
|
714
716
|
"""
|
|
715
717
|
configdirs = []
|
|
716
718
|
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
if 'VIRTUAL_ENV' in os.environ:
|
|
721
|
-
configdirs.append('%s/etc/' % os.environ['VIRTUAL_ENV'])
|
|
722
|
-
|
|
723
|
-
if 'CONDA_PREFIX' in os.environ:
|
|
724
|
-
configdirs.append('%s/etc/' % os.environ['CONDA_PREFIX'])
|
|
719
|
+
env_vars = ['RUCIO_HOME', 'VIRTUAL_ENV', 'CONDA_PREFIX']
|
|
720
|
+
configdirs.extend([os.path.join(os.environ[var], 'etc', '') for var in env_vars if var in os.environ])
|
|
725
721
|
|
|
726
722
|
configdirs.append('/opt/rucio/etc/')
|
|
727
723
|
|
|
@@ -758,21 +754,15 @@ def get_rse_credentials(path_to_credentials_file: Optional[Union[str, os.PathLik
|
|
|
758
754
|
return credentials
|
|
759
755
|
|
|
760
756
|
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
757
|
+
@cache
|
|
764
758
|
def get_config() -> configparser.ConfigParser:
|
|
765
759
|
"""Factory function for the configuration class. Returns the ConfigParser instance."""
|
|
766
|
-
|
|
767
|
-
if __CONFIG is None:
|
|
768
|
-
__CONFIG = Config()
|
|
769
|
-
return __CONFIG.parser
|
|
760
|
+
return Config().parser
|
|
770
761
|
|
|
771
762
|
|
|
772
763
|
def clean_cached_config() -> None:
|
|
773
764
|
"""Deletes the cached config singleton instance."""
|
|
774
|
-
|
|
775
|
-
__CONFIG = None
|
|
765
|
+
get_config.cache_clear()
|
|
776
766
|
|
|
777
767
|
|
|
778
768
|
class Config:
|
|
@@ -795,7 +785,4 @@ class Config:
|
|
|
795
785
|
'\n\t' + '\n\t'.join(configs))
|
|
796
786
|
|
|
797
787
|
if not self.parser.read(self.configfile) == [self.configfile]:
|
|
798
|
-
raise
|
|
799
|
-
'Could not load Rucio configuration file. '
|
|
800
|
-
'Rucio tried loading the following configuration file:'
|
|
801
|
-
'\n\t' + self.configfile)
|
|
788
|
+
raise ConfigLoadingError(self.configfile)
|
rucio/common/constants.py
CHANGED
|
@@ -48,7 +48,7 @@ if config_get_bool('transfers', 'srm_https_compatibility', raise_exception=False
|
|
|
48
48
|
SCHEME_MAP['srm'].append('davs')
|
|
49
49
|
SCHEME_MAP['davs'].append('srm')
|
|
50
50
|
|
|
51
|
-
SORTING_ALGORITHMS_LITERAL = Literal['geoip', '
|
|
51
|
+
SORTING_ALGORITHMS_LITERAL = Literal['geoip', 'custom_table', 'random']
|
|
52
52
|
SORTING_ALGORITHMS = list(get_args(SORTING_ALGORITHMS_LITERAL))
|
|
53
53
|
|
|
54
54
|
SUPPORTED_PROTOCOLS_LITERAL = Literal['gsiftp', 'srm', 'root', 'davs', 'http', 'https', 'file', 'storm', 'srm+https', 'scp', 'rsync', 'rclone', 'magnet']
|
|
@@ -56,8 +56,11 @@ SUPPORTED_PROTOCOLS: list[str] = list(get_args(SUPPORTED_PROTOCOLS_LITERAL))
|
|
|
56
56
|
|
|
57
57
|
RSE_SUPPORTED_PROTOCOL_DOMAINS_LITERAL = Literal['ALL', 'LAN', 'WAN']
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
RSE_BASE_SUPPORTED_PROTOCOL_OPERATIONS_LITERAL = Literal['read', 'write', 'delete']
|
|
60
|
+
RSE_BASE_SUPPORTED_PROTOCOL_OPERATIONS: list[str] = list(get_args(RSE_BASE_SUPPORTED_PROTOCOL_OPERATIONS_LITERAL))
|
|
61
|
+
|
|
62
|
+
RSE_ALL_SUPPORTED_PROTOCOL_OPERATIONS_LITERAL = Literal[RSE_BASE_SUPPORTED_PROTOCOL_OPERATIONS_LITERAL, 'third_party_copy_read', 'third_party_copy_write']
|
|
63
|
+
RSE_ALL_SUPPORTED_PROTOCOL_OPERATIONS: list[str] = list(get_args(RSE_ALL_SUPPORTED_PROTOCOL_OPERATIONS_LITERAL))
|
|
61
64
|
|
|
62
65
|
FTS_STATE = namedtuple('FTS_STATE', ['SUBMITTED', 'READY', 'ACTIVE', 'FAILED', 'FINISHED', 'FINISHEDDIRTY', 'NOT_USED',
|
|
63
66
|
'CANCELED'])('SUBMITTED', 'READY', 'ACTIVE', 'FAILED', 'FINISHED', 'FINISHEDDIRTY',
|
|
@@ -74,12 +77,14 @@ FTS_JOB_TYPE = namedtuple('FTS_JOB_TYPE', ['MULTIPLE_REPLICA', 'MULTI_HOP', 'SES
|
|
|
74
77
|
MAX_MESSAGE_LENGTH = 4000
|
|
75
78
|
|
|
76
79
|
|
|
80
|
+
@enum.unique
|
|
77
81
|
class SuspiciousAvailability(enum.Enum):
|
|
78
82
|
ALL = 0
|
|
79
83
|
EXIST_COPIES = 1
|
|
80
84
|
LAST_COPY = 2
|
|
81
85
|
|
|
82
86
|
|
|
87
|
+
@enum.unique
|
|
83
88
|
class ReplicaState(enum.Enum):
|
|
84
89
|
# From rucio.db.sqla.constants, update that file at the same time as this
|
|
85
90
|
AVAILABLE = 'A'
|
|
@@ -157,3 +162,56 @@ class RseAttr:
|
|
|
157
162
|
DEFAULT_LIMIT_FILES = 'default_limit_files'
|
|
158
163
|
QUOTA_APPROVERS = 'quota_approvers'
|
|
159
164
|
RULE_DELETERS = 'rule_deleters'
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
# Literal types to allow overloading of functions with RSE attributes in their signature.
|
|
168
|
+
# RSE attributes are encoded via the BooleanString decorator as VARCHAR in the database,
|
|
169
|
+
# but they are used as either bool or string in the code.
|
|
170
|
+
# This is only determined at runtime, so for static type checking
|
|
171
|
+
# we need to manually specify which attrs are string and which are bool.
|
|
172
|
+
# In future, we could refactor RseAttr to avoid code duplication.
|
|
173
|
+
RSE_ATTRS_STR = Literal[
|
|
174
|
+
'archive_timeout',
|
|
175
|
+
'associated_sites',
|
|
176
|
+
'bittorrent_tracker_addr',
|
|
177
|
+
'country',
|
|
178
|
+
'decommission',
|
|
179
|
+
'default_account_limit_bytes',
|
|
180
|
+
'fts',
|
|
181
|
+
'globus_endpoint_id',
|
|
182
|
+
'lfn2pfn_algorithm',
|
|
183
|
+
'maximum_pin_lifetime',
|
|
184
|
+
'multihop_tombstone_delay',
|
|
185
|
+
'naming_convention',
|
|
186
|
+
'oidc_base_path',
|
|
187
|
+
'oidc_support'
|
|
188
|
+
'physgroup',
|
|
189
|
+
'qbittorrent_management_address'
|
|
190
|
+
'rule_approvers',
|
|
191
|
+
's3_url_style',
|
|
192
|
+
'simulate_multirange',
|
|
193
|
+
'site',
|
|
194
|
+
'source_for_total_space',
|
|
195
|
+
'source_for_used_space',
|
|
196
|
+
'staging_buffer',
|
|
197
|
+
'tombstone_delay',
|
|
198
|
+
'type'
|
|
199
|
+
]
|
|
200
|
+
|
|
201
|
+
RSE_ATTRS_BOOL = Literal[
|
|
202
|
+
'auto_approve_bytes',
|
|
203
|
+
'auto_approve_files',
|
|
204
|
+
'block_manual_approval',
|
|
205
|
+
'greedyDeletion',
|
|
206
|
+
'is_object_store',
|
|
207
|
+
'restricted_read',
|
|
208
|
+
'restricted_write',
|
|
209
|
+
'skip_upload_stat',
|
|
210
|
+
'staging_required',
|
|
211
|
+
'strict_copy',
|
|
212
|
+
'use_ipv4',
|
|
213
|
+
'verify_checksum'
|
|
214
|
+
]
|
|
215
|
+
|
|
216
|
+
SUPPORTED_SIGN_URL_SERVICES_LITERAL = Literal['gcs', 's3', 'swift']
|
|
217
|
+
SUPPORTED_SIGN_URL_SERVICES = list(get_args(SUPPORTED_SIGN_URL_SERVICES_LITERAL))
|
rucio/common/didtype.py
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
DID type to represent a did and to simplify operations on it
|
|
17
17
|
"""
|
|
18
18
|
|
|
19
|
-
from typing import Union
|
|
19
|
+
from typing import Any, Union
|
|
20
20
|
|
|
21
21
|
from rucio.common.exception import DIDError
|
|
22
22
|
|
|
@@ -58,6 +58,22 @@ class DID:
|
|
|
58
58
|
self.scope: str = ''
|
|
59
59
|
self.name: str = ''
|
|
60
60
|
|
|
61
|
+
did = self._parse_did_from_args(*args, **kwargs)
|
|
62
|
+
|
|
63
|
+
self._construct_did(did)
|
|
64
|
+
|
|
65
|
+
if not self.is_valid_format():
|
|
66
|
+
raise DIDError('Object has invalid format after construction: {}'.format(str(self)))
|
|
67
|
+
|
|
68
|
+
def _parse_did_from_args(
|
|
69
|
+
self,
|
|
70
|
+
*args,
|
|
71
|
+
**kwargs
|
|
72
|
+
) -> Union["DID", str, tuple[str, str], list[str], dict[str, str]]:
|
|
73
|
+
"""
|
|
74
|
+
Parse the DID object from the given arguments
|
|
75
|
+
:return: DID object
|
|
76
|
+
"""
|
|
61
77
|
num_args = len(args)
|
|
62
78
|
num_kwargs = len(kwargs)
|
|
63
79
|
if (num_args + num_kwargs) > 2:
|
|
@@ -77,46 +93,78 @@ class DID:
|
|
|
77
93
|
else:
|
|
78
94
|
raise DIDError('Constructor got unexpected keyword argument: {}'.format(k))
|
|
79
95
|
else:
|
|
80
|
-
raise DIDError('First argument of constructor is expected to be string type'
|
|
96
|
+
raise DIDError('First argument of constructor is expected to be string type '
|
|
81
97
|
'when keyword argument is given. Given type: {}'.format(type(did)))
|
|
82
98
|
elif num_args == 0:
|
|
83
99
|
did = kwargs.get('did', kwargs)
|
|
84
100
|
else:
|
|
85
101
|
did = args
|
|
102
|
+
return did
|
|
86
103
|
|
|
104
|
+
def _construct_did(self, did: Any) -> None:
|
|
105
|
+
"""
|
|
106
|
+
Construct the DID object from the given input.
|
|
107
|
+
|
|
108
|
+
:param did: input to construct the DID object from
|
|
109
|
+
"""
|
|
87
110
|
if isinstance(did, dict):
|
|
88
|
-
self.
|
|
89
|
-
self.name = did.get('name', '')
|
|
90
|
-
if not self.has_scope():
|
|
91
|
-
self.update_implicit_scope()
|
|
111
|
+
self._did_from_dict(did)
|
|
92
112
|
elif isinstance(did, tuple) or isinstance(did, list):
|
|
93
|
-
|
|
94
|
-
raise DIDError('Construction from tuple or list requires exactly 2 elements')
|
|
95
|
-
self.scope = did[0]
|
|
96
|
-
self.name = did[1]
|
|
113
|
+
self._did_from_list_or_tuple(did)
|
|
97
114
|
elif isinstance(did, str):
|
|
98
|
-
|
|
99
|
-
if len(did_parts) == 1:
|
|
100
|
-
self.name = did
|
|
101
|
-
self.update_implicit_scope()
|
|
102
|
-
if not self.has_scope():
|
|
103
|
-
raise DIDError('Object construction from non-splitable string is ambigious')
|
|
104
|
-
else:
|
|
105
|
-
self.scope = did_parts[0]
|
|
106
|
-
self.name = did_parts[1]
|
|
115
|
+
self._did_from_str(did)
|
|
107
116
|
elif isinstance(did, DID):
|
|
108
|
-
self.
|
|
109
|
-
self.name = did.name
|
|
117
|
+
self._did_from_did_object(did)
|
|
110
118
|
else:
|
|
111
119
|
raise DIDError('Cannot build object from: {}'.format(type(did)))
|
|
112
120
|
|
|
113
121
|
if self.name.endswith('/'):
|
|
114
122
|
self.name = self.name[:-1]
|
|
115
123
|
|
|
116
|
-
|
|
117
|
-
|
|
124
|
+
def _did_from_str(self, did: str) -> None:
|
|
125
|
+
"""
|
|
126
|
+
Construct the DID from a string.
|
|
127
|
+
:param did: string containing the DID information
|
|
128
|
+
"""
|
|
129
|
+
did_parts = did.split(DID.SCOPE_SEPARATOR, 1)
|
|
130
|
+
if len(did_parts) == 1:
|
|
131
|
+
self.name = did
|
|
132
|
+
self._update_implicit_scope()
|
|
133
|
+
if not self.has_scope():
|
|
134
|
+
raise DIDError('Object construction from non-splitable string is ambigious')
|
|
135
|
+
else:
|
|
136
|
+
self.scope = did_parts[0]
|
|
137
|
+
self.name = did_parts[1]
|
|
138
|
+
|
|
139
|
+
def _did_from_dict(self, did: dict[str, str]) -> None:
|
|
140
|
+
"""
|
|
141
|
+
Construct the DID from a dictionary.
|
|
142
|
+
:param did: dictionary optionally containing the keys 'scope' and 'name'
|
|
143
|
+
"""
|
|
144
|
+
self.scope = did.get('scope', '')
|
|
145
|
+
self.name = did.get('name', '')
|
|
146
|
+
if not self.has_scope():
|
|
147
|
+
self._update_implicit_scope()
|
|
148
|
+
|
|
149
|
+
def _did_from_list_or_tuple(self, did: Union[list[str], tuple[str, str]]) -> None:
|
|
150
|
+
"""
|
|
151
|
+
Construct the DID from a list or tuple.
|
|
152
|
+
:param did: list or tuple with expected length of 2
|
|
153
|
+
"""
|
|
154
|
+
if len(did) != 2:
|
|
155
|
+
raise DIDError('Construction from tuple or list requires exactly 2 elements. Number of elements passed: %i' % len(did))
|
|
156
|
+
self.scope = did[0]
|
|
157
|
+
self.name = did[1]
|
|
158
|
+
|
|
159
|
+
def _did_from_did_object(self, did: "DID") -> None:
|
|
160
|
+
"""
|
|
161
|
+
Construct the DID from another DID object.
|
|
162
|
+
:param did: DID object
|
|
163
|
+
"""
|
|
164
|
+
self.scope = did.scope
|
|
165
|
+
self.name = did.name
|
|
118
166
|
|
|
119
|
-
def
|
|
167
|
+
def _update_implicit_scope(self) -> None:
|
|
120
168
|
"""
|
|
121
169
|
This method sets the scope if it is implicitly given in self.name
|
|
122
170
|
"""
|
rucio/common/dumper/__init__.py
CHANGED
|
@@ -21,14 +21,21 @@ import os
|
|
|
21
21
|
import re
|
|
22
22
|
import sys
|
|
23
23
|
import tempfile
|
|
24
|
+
from configparser import NoOptionError, NoSectionError
|
|
25
|
+
from functools import cache
|
|
24
26
|
from typing import TYPE_CHECKING, Any, Literal, Optional, Union
|
|
25
27
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
try:
|
|
29
|
+
# requires non-Python package pre-requisites that break unit tests
|
|
30
|
+
import gfal2
|
|
31
|
+
except ModuleNotFoundError:
|
|
32
|
+
pass
|
|
33
|
+
|
|
28
34
|
import requests
|
|
35
|
+
from magic import Magic
|
|
29
36
|
|
|
30
37
|
from rucio.common import config
|
|
31
|
-
from rucio.
|
|
38
|
+
from rucio.common.exception import ConfigNotFound
|
|
32
39
|
|
|
33
40
|
if TYPE_CHECKING:
|
|
34
41
|
from collections.abc import Iterator
|
|
@@ -70,7 +77,7 @@ def error(text: str, exit_code: int = 1) -> None:
|
|
|
70
77
|
logger = logging.getLogger('dumper.__init__')
|
|
71
78
|
logger.error(text)
|
|
72
79
|
sys.stderr.write(text + '\n')
|
|
73
|
-
exit(
|
|
80
|
+
exit(exit_code)
|
|
74
81
|
|
|
75
82
|
|
|
76
83
|
def mkdir(dir_: "StrOrBytesPath") -> None:
|
|
@@ -89,14 +96,21 @@ def cacert_config(config: "ModuleType", rucio_home: str) -> Optional[Union["File
|
|
|
89
96
|
logger = logging.getLogger('dumper.__init__')
|
|
90
97
|
try:
|
|
91
98
|
cacert = config.config_get('client', 'ca_cert').replace('$RUCIO_HOME', rucio_home)
|
|
92
|
-
except
|
|
99
|
+
except ConfigNotFound:
|
|
100
|
+
logger.warning('Configuration file not found.')
|
|
101
|
+
cacert = None
|
|
102
|
+
except (KeyError, NoOptionError, NoSectionError):
|
|
103
|
+
logger.warning('CA Certificate configuration not found.')
|
|
93
104
|
cacert = None
|
|
94
105
|
|
|
95
|
-
if cacert
|
|
96
|
-
|
|
97
|
-
|
|
106
|
+
if cacert and os.path.exists(cacert):
|
|
107
|
+
return cacert
|
|
108
|
+
else:
|
|
109
|
+
logger.warning('Configured CA Certificate file "%s" not found', cacert)
|
|
110
|
+
|
|
111
|
+
logger.warning('Host certificate verification disabled.')
|
|
98
112
|
|
|
99
|
-
return
|
|
113
|
+
return False
|
|
100
114
|
|
|
101
115
|
|
|
102
116
|
def rucio_home() -> str:
|
|
@@ -115,41 +129,37 @@ RESULTS_DIR = 'results'
|
|
|
115
129
|
CHUNK_SIZE = 4194304 # 4MiB
|
|
116
130
|
|
|
117
131
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
mimetype = _mime.file
|
|
125
|
-
else:
|
|
126
|
-
mimetype = lambda filename: magic.from_file(filename, mime=True) # NOQA
|
|
127
|
-
# pylint: enable=no-member
|
|
132
|
+
@cache
|
|
133
|
+
def get_libmagic_wrapper() -> Magic:
|
|
134
|
+
"""
|
|
135
|
+
Returns a libmagic wrapper.
|
|
136
|
+
"""
|
|
137
|
+
return Magic(mime=True)
|
|
128
138
|
|
|
129
139
|
|
|
130
|
-
def
|
|
131
|
-
|
|
140
|
+
def is_plaintext(filename: "GenericPath") -> bool:
|
|
141
|
+
"""
|
|
132
142
|
Returns True if `filename` has mimetype == 'text/plain'.
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
return mimetype(filename).split(';')[0] == 'text/plain'
|
|
143
|
+
"""
|
|
144
|
+
mime = get_libmagic_wrapper()
|
|
145
|
+
return mime.from_file(filename) == 'text/plain'
|
|
137
146
|
|
|
138
147
|
|
|
139
|
-
def smart_open(filename: "GenericPath") -> Optional[
|
|
148
|
+
def smart_open(filename: "GenericPath") -> Optional["TextIO"]:
|
|
140
149
|
'''
|
|
141
150
|
Returns an open file object if `filename` is plain text, else assumes
|
|
142
151
|
it is a bzip2 compressed file and returns a file-like object to
|
|
143
152
|
handle it.
|
|
144
153
|
'''
|
|
145
154
|
f = None
|
|
146
|
-
if
|
|
155
|
+
if is_plaintext(filename):
|
|
147
156
|
f = open(filename, 'rt')
|
|
148
157
|
else:
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
158
|
+
mime = get_libmagic_wrapper()
|
|
159
|
+
file_type = mime.from_file(filename)
|
|
160
|
+
if file_type in ['application/gzip', 'application/x-gzip']:
|
|
161
|
+
f = gzip.open(filename, 'rt')
|
|
162
|
+
elif file_type == 'application/x-bzip2':
|
|
153
163
|
f = bz2.open(filename, 'rt')
|
|
154
164
|
else:
|
|
155
165
|
pass # Not supported format
|
|
@@ -204,8 +214,6 @@ def temp_file(
|
|
|
204
214
|
|
|
205
215
|
|
|
206
216
|
DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S'
|
|
207
|
-
DATETIME_FORMAT_FULL = '%Y-%m-%dT%H:%M:%S'
|
|
208
|
-
MILLISECONDS_RE = re.compile(r'\.(\d{3})Z$')
|
|
209
217
|
|
|
210
218
|
|
|
211
219
|
def to_datetime(str_or_datetime: Union[datetime.datetime, str]) -> Optional[datetime.datetime]:
|
|
@@ -236,17 +244,16 @@ def to_datetime(str_or_datetime: Union[datetime.datetime, str]) -> Optional[date
|
|
|
236
244
|
'Trying to parse "%s" date with resolution of milliseconds',
|
|
237
245
|
str_or_datetime,
|
|
238
246
|
)
|
|
239
|
-
milliseconds = int(MILLISECONDS_RE.search(str_or_datetime).group(1))
|
|
240
|
-
str_or_datetime = MILLISECONDS_RE.sub('', str_or_datetime)
|
|
241
247
|
date = datetime.datetime.strptime(
|
|
242
248
|
str_or_datetime,
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
249
|
+
# Adding milliseconds to the datetime format
|
|
250
|
+
DATETIME_FORMAT + '.%f',
|
|
251
|
+
).replace(microsecond=0)
|
|
246
252
|
return date
|
|
247
253
|
|
|
248
254
|
|
|
249
255
|
def ddmendpoint_preferred_protocol(ddmendpoint: str) -> "RSEProtocolDict":
|
|
256
|
+
from rucio.core.rse import get_rse_id, get_rse_protocols # pylint: disable=import-outside-toplevel
|
|
250
257
|
return next(p for p in get_rse_protocols(get_rse_id(ddmendpoint))['protocols'] if p['domains']['wan']['read'] == 1)
|
|
251
258
|
|
|
252
259
|
|