rucio 35.7.0__py3-none-any.whl → 37.0.0__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (266) hide show
  1. rucio/alembicrevision.py +1 -1
  2. rucio/{daemons/c3po/collectors → cli}/__init__.py +1 -0
  3. rucio/cli/account.py +216 -0
  4. rucio-35.7.0.data/scripts/rucio → rucio/cli/bin_legacy/rucio.py +769 -486
  5. rucio-35.7.0.data/scripts/rucio-admin → rucio/cli/bin_legacy/rucio_admin.py +476 -423
  6. rucio/cli/command.py +272 -0
  7. rucio/cli/config.py +72 -0
  8. rucio/cli/did.py +191 -0
  9. rucio/cli/download.py +128 -0
  10. rucio/cli/lifetime_exception.py +33 -0
  11. rucio/cli/replica.py +162 -0
  12. rucio/cli/rse.py +293 -0
  13. rucio/cli/rule.py +158 -0
  14. rucio/cli/scope.py +40 -0
  15. rucio/cli/subscription.py +73 -0
  16. rucio/cli/upload.py +60 -0
  17. rucio/cli/utils.py +226 -0
  18. rucio/client/accountclient.py +0 -1
  19. rucio/client/baseclient.py +33 -24
  20. rucio/client/client.py +45 -1
  21. rucio/client/didclient.py +5 -3
  22. rucio/client/downloadclient.py +6 -8
  23. rucio/client/replicaclient.py +0 -2
  24. rucio/client/richclient.py +317 -0
  25. rucio/client/rseclient.py +4 -4
  26. rucio/client/uploadclient.py +26 -12
  27. rucio/common/bittorrent.py +234 -0
  28. rucio/common/cache.py +66 -29
  29. rucio/common/checksum.py +168 -0
  30. rucio/common/client.py +122 -0
  31. rucio/common/config.py +22 -35
  32. rucio/common/constants.py +61 -3
  33. rucio/common/didtype.py +72 -24
  34. rucio/common/dumper/__init__.py +45 -38
  35. rucio/common/dumper/consistency.py +75 -30
  36. rucio/common/dumper/data_models.py +63 -19
  37. rucio/common/dumper/path_parsing.py +19 -8
  38. rucio/common/exception.py +65 -8
  39. rucio/common/extra.py +5 -10
  40. rucio/common/logging.py +13 -13
  41. rucio/common/pcache.py +8 -7
  42. rucio/common/plugins.py +59 -27
  43. rucio/common/policy.py +12 -3
  44. rucio/common/schema/__init__.py +84 -34
  45. rucio/common/schema/generic.py +0 -17
  46. rucio/common/schema/generic_multi_vo.py +0 -17
  47. rucio/common/test_rucio_server.py +12 -6
  48. rucio/common/types.py +132 -52
  49. rucio/common/utils.py +93 -643
  50. rucio/core/account_limit.py +14 -12
  51. rucio/core/authentication.py +2 -2
  52. rucio/core/config.py +23 -42
  53. rucio/core/credential.py +14 -15
  54. rucio/core/did.py +5 -1
  55. rucio/core/did_meta_plugins/elasticsearch_meta.py +407 -0
  56. rucio/core/did_meta_plugins/filter_engine.py +62 -3
  57. rucio/core/did_meta_plugins/json_meta.py +2 -2
  58. rucio/core/did_meta_plugins/mongo_meta.py +43 -30
  59. rucio/core/did_meta_plugins/postgres_meta.py +75 -39
  60. rucio/core/identity.py +6 -5
  61. rucio/core/importer.py +4 -3
  62. rucio/core/lifetime_exception.py +2 -2
  63. rucio/core/lock.py +8 -7
  64. rucio/core/message.py +6 -0
  65. rucio/core/monitor.py +30 -29
  66. rucio/core/naming_convention.py +2 -2
  67. rucio/core/nongrid_trace.py +2 -2
  68. rucio/core/oidc.py +11 -9
  69. rucio/core/permission/__init__.py +79 -37
  70. rucio/core/permission/generic.py +1 -7
  71. rucio/core/permission/generic_multi_vo.py +1 -7
  72. rucio/core/quarantined_replica.py +4 -3
  73. rucio/core/replica.py +464 -139
  74. rucio/core/replica_sorter.py +55 -59
  75. rucio/core/request.py +34 -32
  76. rucio/core/rse.py +301 -97
  77. rucio/core/rse_counter.py +1 -2
  78. rucio/core/rse_expression_parser.py +7 -7
  79. rucio/core/rse_selector.py +9 -7
  80. rucio/core/rule.py +41 -40
  81. rucio/core/rule_grouping.py +42 -40
  82. rucio/core/scope.py +5 -4
  83. rucio/core/subscription.py +26 -28
  84. rucio/core/topology.py +11 -11
  85. rucio/core/trace.py +2 -2
  86. rucio/core/transfer.py +29 -15
  87. rucio/core/volatile_replica.py +4 -3
  88. rucio/daemons/atropos/atropos.py +1 -1
  89. rucio/daemons/auditor/__init__.py +2 -2
  90. rucio/daemons/auditor/srmdumps.py +6 -6
  91. rucio/daemons/automatix/automatix.py +32 -21
  92. rucio/daemons/badreplicas/necromancer.py +2 -2
  93. rucio/daemons/bb8/nuclei_background_rebalance.py +1 -1
  94. rucio/daemons/bb8/t2_background_rebalance.py +1 -1
  95. rucio/daemons/common.py +15 -25
  96. rucio/daemons/conveyor/finisher.py +2 -2
  97. rucio/daemons/conveyor/poller.py +18 -28
  98. rucio/daemons/conveyor/receiver.py +2 -2
  99. rucio/daemons/conveyor/stager.py +1 -0
  100. rucio/daemons/conveyor/submitter.py +3 -3
  101. rucio/daemons/hermes/hermes.py +91 -30
  102. rucio/daemons/judge/evaluator.py +2 -2
  103. rucio/daemons/oauthmanager/oauthmanager.py +3 -3
  104. rucio/daemons/reaper/dark_reaper.py +7 -3
  105. rucio/daemons/reaper/reaper.py +12 -16
  106. rucio/daemons/rsedecommissioner/config.py +1 -1
  107. rucio/daemons/rsedecommissioner/profiles/generic.py +5 -4
  108. rucio/daemons/rsedecommissioner/profiles/types.py +7 -6
  109. rucio/daemons/rsedecommissioner/rse_decommissioner.py +1 -1
  110. rucio/daemons/storage/consistency/actions.py +8 -6
  111. rucio/daemons/tracer/kronos.py +4 -4
  112. rucio/db/sqla/constants.py +5 -0
  113. rucio/db/sqla/migrate_repo/versions/1677d4d803c8_split_rse_availability_into_multiple.py +4 -4
  114. rucio/db/sqla/migrate_repo/versions/30d5206e9cad_increase_oauthrequest_redirect_msg_.py +37 -0
  115. rucio/db/sqla/models.py +157 -154
  116. rucio/db/sqla/session.py +58 -27
  117. rucio/db/sqla/types.py +2 -2
  118. rucio/db/sqla/util.py +2 -2
  119. rucio/gateway/account.py +18 -12
  120. rucio/gateway/account_limit.py +137 -60
  121. rucio/gateway/authentication.py +18 -12
  122. rucio/gateway/config.py +30 -20
  123. rucio/gateway/credential.py +9 -10
  124. rucio/gateway/did.py +70 -53
  125. rucio/gateway/dirac.py +6 -4
  126. rucio/gateway/exporter.py +3 -2
  127. rucio/gateway/heartbeat.py +6 -4
  128. rucio/gateway/identity.py +36 -51
  129. rucio/gateway/importer.py +3 -2
  130. rucio/gateway/lifetime_exception.py +3 -2
  131. rucio/gateway/meta_conventions.py +17 -6
  132. rucio/gateway/permission.py +4 -1
  133. rucio/gateway/quarantined_replica.py +3 -2
  134. rucio/gateway/replica.py +31 -22
  135. rucio/gateway/request.py +27 -18
  136. rucio/gateway/rse.py +69 -37
  137. rucio/gateway/rule.py +46 -26
  138. rucio/gateway/scope.py +3 -2
  139. rucio/gateway/subscription.py +14 -11
  140. rucio/gateway/vo.py +12 -8
  141. rucio/rse/__init__.py +3 -3
  142. rucio/rse/protocols/bittorrent.py +11 -1
  143. rucio/rse/protocols/cache.py +0 -11
  144. rucio/rse/protocols/dummy.py +0 -11
  145. rucio/rse/protocols/gfal.py +14 -9
  146. rucio/rse/protocols/globus.py +1 -1
  147. rucio/rse/protocols/http_cache.py +1 -1
  148. rucio/rse/protocols/posix.py +2 -2
  149. rucio/rse/protocols/protocol.py +84 -317
  150. rucio/rse/protocols/rclone.py +2 -1
  151. rucio/rse/protocols/rfio.py +10 -1
  152. rucio/rse/protocols/ssh.py +2 -1
  153. rucio/rse/protocols/storm.py +2 -13
  154. rucio/rse/protocols/webdav.py +74 -30
  155. rucio/rse/protocols/xrootd.py +2 -1
  156. rucio/rse/rsemanager.py +170 -53
  157. rucio/rse/translation.py +260 -0
  158. rucio/tests/common.py +23 -13
  159. rucio/tests/common_server.py +26 -9
  160. rucio/transfertool/bittorrent.py +15 -14
  161. rucio/transfertool/bittorrent_driver.py +5 -7
  162. rucio/transfertool/bittorrent_driver_qbittorrent.py +9 -8
  163. rucio/transfertool/fts3.py +20 -16
  164. rucio/transfertool/mock.py +2 -3
  165. rucio/vcsversion.py +4 -4
  166. rucio/version.py +7 -0
  167. rucio/web/rest/flaskapi/v1/accounts.py +17 -3
  168. rucio/web/rest/flaskapi/v1/auth.py +5 -5
  169. rucio/web/rest/flaskapi/v1/credentials.py +3 -2
  170. rucio/web/rest/flaskapi/v1/dids.py +21 -15
  171. rucio/web/rest/flaskapi/v1/identities.py +33 -9
  172. rucio/web/rest/flaskapi/v1/redirect.py +5 -4
  173. rucio/web/rest/flaskapi/v1/replicas.py +12 -8
  174. rucio/web/rest/flaskapi/v1/rses.py +15 -4
  175. rucio/web/rest/flaskapi/v1/traces.py +56 -19
  176. {rucio-35.7.0.data → rucio-37.0.0.data}/data/rucio/etc/alembic.ini.template +1 -1
  177. {rucio-35.7.0.data → rucio-37.0.0.data}/data/rucio/etc/alembic_offline.ini.template +1 -1
  178. {rucio-35.7.0.data → rucio-37.0.0.data}/data/rucio/etc/rucio.cfg.atlas.client.template +3 -2
  179. {rucio-35.7.0.data → rucio-37.0.0.data}/data/rucio/etc/rucio.cfg.template +3 -19
  180. {rucio-35.7.0.data → rucio-37.0.0.data}/data/rucio/etc/rucio_multi_vo.cfg.template +1 -18
  181. {rucio-35.7.0.data → rucio-37.0.0.data}/data/rucio/requirements.server.txt +97 -68
  182. rucio-37.0.0.data/scripts/rucio +133 -0
  183. rucio-37.0.0.data/scripts/rucio-admin +97 -0
  184. {rucio-35.7.0.data → rucio-37.0.0.data}/scripts/rucio-atropos +2 -2
  185. {rucio-35.7.0.data → rucio-37.0.0.data}/scripts/rucio-auditor +2 -1
  186. {rucio-35.7.0.data → rucio-37.0.0.data}/scripts/rucio-automatix +2 -2
  187. {rucio-35.7.0.data → rucio-37.0.0.data}/scripts/rucio-cache-client +17 -10
  188. {rucio-35.7.0.data → rucio-37.0.0.data}/scripts/rucio-conveyor-receiver +1 -0
  189. {rucio-35.7.0.data → rucio-37.0.0.data}/scripts/rucio-kronos +1 -0
  190. {rucio-35.7.0.data → rucio-37.0.0.data}/scripts/rucio-minos +2 -2
  191. {rucio-35.7.0.data → rucio-37.0.0.data}/scripts/rucio-minos-temporary-expiration +2 -2
  192. {rucio-35.7.0.data → rucio-37.0.0.data}/scripts/rucio-necromancer +2 -2
  193. {rucio-35.7.0.data → rucio-37.0.0.data}/scripts/rucio-reaper +6 -6
  194. {rucio-35.7.0.data → rucio-37.0.0.data}/scripts/rucio-transmogrifier +2 -2
  195. rucio-37.0.0.dist-info/METADATA +92 -0
  196. {rucio-35.7.0.dist-info → rucio-37.0.0.dist-info}/RECORD +237 -243
  197. {rucio-35.7.0.dist-info → rucio-37.0.0.dist-info}/licenses/AUTHORS.rst +3 -0
  198. rucio/common/schema/atlas.py +0 -413
  199. rucio/common/schema/belleii.py +0 -408
  200. rucio/common/schema/domatpc.py +0 -401
  201. rucio/common/schema/escape.py +0 -426
  202. rucio/common/schema/icecube.py +0 -406
  203. rucio/core/permission/atlas.py +0 -1348
  204. rucio/core/permission/belleii.py +0 -1077
  205. rucio/core/permission/escape.py +0 -1078
  206. rucio/daemons/c3po/algorithms/__init__.py +0 -13
  207. rucio/daemons/c3po/algorithms/simple.py +0 -134
  208. rucio/daemons/c3po/algorithms/t2_free_space.py +0 -128
  209. rucio/daemons/c3po/algorithms/t2_free_space_only_pop.py +0 -130
  210. rucio/daemons/c3po/algorithms/t2_free_space_only_pop_with_network.py +0 -294
  211. rucio/daemons/c3po/c3po.py +0 -371
  212. rucio/daemons/c3po/collectors/agis.py +0 -108
  213. rucio/daemons/c3po/collectors/free_space.py +0 -81
  214. rucio/daemons/c3po/collectors/jedi_did.py +0 -57
  215. rucio/daemons/c3po/collectors/mock_did.py +0 -51
  216. rucio/daemons/c3po/collectors/network_metrics.py +0 -71
  217. rucio/daemons/c3po/collectors/workload.py +0 -112
  218. rucio/daemons/c3po/utils/__init__.py +0 -13
  219. rucio/daemons/c3po/utils/dataset_cache.py +0 -50
  220. rucio/daemons/c3po/utils/expiring_dataset_cache.py +0 -56
  221. rucio/daemons/c3po/utils/expiring_list.py +0 -62
  222. rucio/daemons/c3po/utils/popularity.py +0 -85
  223. rucio/daemons/c3po/utils/timeseries.py +0 -89
  224. rucio/rse/protocols/gsiftp.py +0 -92
  225. rucio-35.7.0.data/scripts/rucio-c3po +0 -85
  226. rucio-35.7.0.dist-info/METADATA +0 -72
  227. /rucio/{daemons/c3po → cli/bin_legacy}/__init__.py +0 -0
  228. {rucio-35.7.0.data → rucio-37.0.0.data}/data/rucio/etc/globus-config.yml.template +0 -0
  229. {rucio-35.7.0.data → rucio-37.0.0.data}/data/rucio/etc/ldap.cfg.template +0 -0
  230. {rucio-35.7.0.data → rucio-37.0.0.data}/data/rucio/etc/mail_templates/rule_approval_request.tmpl +0 -0
  231. {rucio-35.7.0.data → rucio-37.0.0.data}/data/rucio/etc/mail_templates/rule_approved_admin.tmpl +0 -0
  232. {rucio-35.7.0.data → rucio-37.0.0.data}/data/rucio/etc/mail_templates/rule_approved_user.tmpl +0 -0
  233. {rucio-35.7.0.data → rucio-37.0.0.data}/data/rucio/etc/mail_templates/rule_denied_admin.tmpl +0 -0
  234. {rucio-35.7.0.data → rucio-37.0.0.data}/data/rucio/etc/mail_templates/rule_denied_user.tmpl +0 -0
  235. {rucio-35.7.0.data → rucio-37.0.0.data}/data/rucio/etc/mail_templates/rule_ok_notification.tmpl +0 -0
  236. {rucio-35.7.0.data → rucio-37.0.0.data}/data/rucio/etc/rse-accounts.cfg.template +0 -0
  237. {rucio-35.7.0.data → rucio-37.0.0.data}/data/rucio/tools/bootstrap.py +0 -0
  238. {rucio-35.7.0.data → rucio-37.0.0.data}/data/rucio/tools/merge_rucio_configs.py +0 -0
  239. {rucio-35.7.0.data → rucio-37.0.0.data}/data/rucio/tools/reset_database.py +0 -0
  240. {rucio-35.7.0.data → rucio-37.0.0.data}/scripts/rucio-abacus-account +0 -0
  241. {rucio-35.7.0.data → rucio-37.0.0.data}/scripts/rucio-abacus-collection-replica +0 -0
  242. {rucio-35.7.0.data → rucio-37.0.0.data}/scripts/rucio-abacus-rse +0 -0
  243. {rucio-35.7.0.data → rucio-37.0.0.data}/scripts/rucio-bb8 +0 -0
  244. {rucio-35.7.0.data → rucio-37.0.0.data}/scripts/rucio-cache-consumer +0 -0
  245. {rucio-35.7.0.data → rucio-37.0.0.data}/scripts/rucio-conveyor-finisher +0 -0
  246. {rucio-35.7.0.data → rucio-37.0.0.data}/scripts/rucio-conveyor-poller +0 -0
  247. {rucio-35.7.0.data → rucio-37.0.0.data}/scripts/rucio-conveyor-preparer +0 -0
  248. {rucio-35.7.0.data → rucio-37.0.0.data}/scripts/rucio-conveyor-stager +0 -0
  249. {rucio-35.7.0.data → rucio-37.0.0.data}/scripts/rucio-conveyor-submitter +0 -0
  250. {rucio-35.7.0.data → rucio-37.0.0.data}/scripts/rucio-conveyor-throttler +0 -0
  251. {rucio-35.7.0.data → rucio-37.0.0.data}/scripts/rucio-dark-reaper +0 -0
  252. {rucio-35.7.0.data → rucio-37.0.0.data}/scripts/rucio-dumper +0 -0
  253. {rucio-35.7.0.data → rucio-37.0.0.data}/scripts/rucio-follower +0 -0
  254. {rucio-35.7.0.data → rucio-37.0.0.data}/scripts/rucio-hermes +0 -0
  255. {rucio-35.7.0.data → rucio-37.0.0.data}/scripts/rucio-judge-cleaner +0 -0
  256. {rucio-35.7.0.data → rucio-37.0.0.data}/scripts/rucio-judge-evaluator +0 -0
  257. {rucio-35.7.0.data → rucio-37.0.0.data}/scripts/rucio-judge-injector +0 -0
  258. {rucio-35.7.0.data → rucio-37.0.0.data}/scripts/rucio-judge-repairer +0 -0
  259. {rucio-35.7.0.data → rucio-37.0.0.data}/scripts/rucio-oauth-manager +0 -0
  260. {rucio-35.7.0.data → rucio-37.0.0.data}/scripts/rucio-replica-recoverer +0 -0
  261. {rucio-35.7.0.data → rucio-37.0.0.data}/scripts/rucio-rse-decommissioner +0 -0
  262. {rucio-35.7.0.data → rucio-37.0.0.data}/scripts/rucio-storage-consistency-actions +0 -0
  263. {rucio-35.7.0.data → rucio-37.0.0.data}/scripts/rucio-undertaker +0 -0
  264. {rucio-35.7.0.dist-info → rucio-37.0.0.dist-info}/WHEEL +0 -0
  265. {rucio-35.7.0.dist-info → rucio-37.0.0.dist-info}/licenses/LICENSE +0 -0
  266. {rucio-35.7.0.dist-info → rucio-37.0.0.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 collections.abc import Callable
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.utils import is_client
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: list[str] = ...,
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
- __CONFIG = None
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
- if 'RUCIO_HOME' in os.environ:
718
- configdirs.append('%s/etc/' % os.environ['RUCIO_HOME'])
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
- __CONFIG = None
762
-
763
-
757
+ @cache
764
758
  def get_config() -> configparser.ConfigParser:
765
759
  """Factory function for the configuration class. Returns the ConfigParser instance."""
766
- global __CONFIG
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
- global __CONFIG
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 ConfigNotFound(
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', 'closeness', 'custom_table', 'dynamic', 'ranking', 'random']
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
- RSE_SUPPORTED_PROTOCOL_OPERATIONS_LITERAL = Literal['read', 'write', 'delete', 'third_party_copy_read', 'third_party_copy_write']
60
- RSE_SUPPORTED_PROTOCOL_OPERATIONS: list[str] = list(get_args(RSE_SUPPORTED_PROTOCOL_OPERATIONS_LITERAL))
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.scope = did.get('scope', '')
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
- if len(did) != 2:
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
- did_parts = did.split(DID.SCOPE_SEPARATOR, 1)
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.scope = did.scope
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
- if not self.is_valid_format():
117
- raise DIDError('Object has invalid format after construction: {}'.format(str(self)))
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 update_implicit_scope(self) -> None:
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
  """
@@ -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
- import gfal2
27
- import magic
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.core.rse import get_rse_id, get_rse_protocols
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(1)
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 KeyError:
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 is None or not os.path.exists(cacert):
96
- logger.warning('Configured CA Certificate file "%s" not found: Host certificate verification disabled', cacert)
97
- cacert = False
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 cacert
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
- # There are two Python modules with the name `magic`, luckily both do
119
- # the same thing.
120
- # pylint: disable=no-member
121
- if 'open' in dir(magic):
122
- _mime = magic.open(magic.MAGIC_MIME)
123
- _mime.load()
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 isplaintext(filename: "GenericPath") -> bool:
131
- '''
140
+ def is_plaintext(filename: "GenericPath") -> bool:
141
+ """
132
142
  Returns True if `filename` has mimetype == 'text/plain'.
133
- '''
134
- if os.path.islink(filename):
135
- filename = os.readlink(filename)
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[Union["TextIO", gzip.GzipFile]]:
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 isplaintext(filename):
155
+ if is_plaintext(filename):
147
156
  f = open(filename, 'rt')
148
157
  else:
149
- file_type = mimetype(filename)
150
- if file_type.find('gzip') > -1:
151
- f = gzip.GzipFile(filename, 'rt')
152
- elif file_type.find('bzip2') > -1:
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
- DATETIME_FORMAT,
244
- )
245
- date = date + datetime.timedelta(microseconds=milliseconds * 1000)
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