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.

Files changed (268) 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/stomp_utils.py +383 -119
  48. rucio/common/test_rucio_server.py +12 -6
  49. rucio/common/types.py +132 -52
  50. rucio/common/utils.py +93 -643
  51. rucio/core/account_limit.py +14 -12
  52. rucio/core/authentication.py +2 -2
  53. rucio/core/config.py +23 -42
  54. rucio/core/credential.py +14 -15
  55. rucio/core/did.py +5 -1
  56. rucio/core/did_meta_plugins/elasticsearch_meta.py +407 -0
  57. rucio/core/did_meta_plugins/filter_engine.py +62 -3
  58. rucio/core/did_meta_plugins/json_meta.py +2 -2
  59. rucio/core/did_meta_plugins/mongo_meta.py +43 -30
  60. rucio/core/did_meta_plugins/postgres_meta.py +75 -39
  61. rucio/core/identity.py +6 -5
  62. rucio/core/importer.py +4 -3
  63. rucio/core/lifetime_exception.py +2 -2
  64. rucio/core/lock.py +8 -7
  65. rucio/core/message.py +6 -0
  66. rucio/core/monitor.py +30 -29
  67. rucio/core/naming_convention.py +2 -2
  68. rucio/core/nongrid_trace.py +2 -2
  69. rucio/core/oidc.py +11 -9
  70. rucio/core/permission/__init__.py +79 -37
  71. rucio/core/permission/generic.py +1 -7
  72. rucio/core/permission/generic_multi_vo.py +1 -7
  73. rucio/core/quarantined_replica.py +4 -3
  74. rucio/core/replica.py +464 -139
  75. rucio/core/replica_sorter.py +55 -59
  76. rucio/core/request.py +34 -32
  77. rucio/core/rse.py +301 -97
  78. rucio/core/rse_counter.py +1 -2
  79. rucio/core/rse_expression_parser.py +7 -7
  80. rucio/core/rse_selector.py +9 -7
  81. rucio/core/rule.py +41 -40
  82. rucio/core/rule_grouping.py +42 -40
  83. rucio/core/scope.py +5 -4
  84. rucio/core/subscription.py +26 -28
  85. rucio/core/topology.py +11 -11
  86. rucio/core/trace.py +2 -2
  87. rucio/core/transfer.py +29 -15
  88. rucio/core/volatile_replica.py +4 -3
  89. rucio/daemons/atropos/atropos.py +1 -1
  90. rucio/daemons/auditor/__init__.py +2 -2
  91. rucio/daemons/auditor/srmdumps.py +6 -6
  92. rucio/daemons/automatix/automatix.py +32 -21
  93. rucio/daemons/badreplicas/necromancer.py +2 -2
  94. rucio/daemons/bb8/nuclei_background_rebalance.py +1 -1
  95. rucio/daemons/bb8/t2_background_rebalance.py +1 -1
  96. rucio/daemons/cache/consumer.py +26 -90
  97. rucio/daemons/common.py +15 -25
  98. rucio/daemons/conveyor/finisher.py +2 -2
  99. rucio/daemons/conveyor/poller.py +18 -28
  100. rucio/daemons/conveyor/receiver.py +53 -123
  101. rucio/daemons/conveyor/stager.py +1 -0
  102. rucio/daemons/conveyor/submitter.py +3 -3
  103. rucio/daemons/hermes/hermes.py +129 -369
  104. rucio/daemons/judge/evaluator.py +2 -2
  105. rucio/daemons/oauthmanager/oauthmanager.py +3 -3
  106. rucio/daemons/reaper/dark_reaper.py +7 -3
  107. rucio/daemons/reaper/reaper.py +12 -16
  108. rucio/daemons/rsedecommissioner/config.py +1 -1
  109. rucio/daemons/rsedecommissioner/profiles/generic.py +5 -4
  110. rucio/daemons/rsedecommissioner/profiles/types.py +7 -6
  111. rucio/daemons/rsedecommissioner/rse_decommissioner.py +1 -1
  112. rucio/daemons/storage/consistency/actions.py +8 -6
  113. rucio/daemons/tracer/kronos.py +117 -142
  114. rucio/db/sqla/constants.py +5 -0
  115. rucio/db/sqla/migrate_repo/versions/1677d4d803c8_split_rse_availability_into_multiple.py +4 -4
  116. rucio/db/sqla/migrate_repo/versions/30d5206e9cad_increase_oauthrequest_redirect_msg_.py +37 -0
  117. rucio/db/sqla/models.py +157 -154
  118. rucio/db/sqla/session.py +58 -27
  119. rucio/db/sqla/types.py +2 -2
  120. rucio/db/sqla/util.py +2 -2
  121. rucio/gateway/account.py +18 -12
  122. rucio/gateway/account_limit.py +137 -60
  123. rucio/gateway/authentication.py +18 -12
  124. rucio/gateway/config.py +30 -20
  125. rucio/gateway/credential.py +9 -10
  126. rucio/gateway/did.py +70 -53
  127. rucio/gateway/dirac.py +6 -4
  128. rucio/gateway/exporter.py +3 -2
  129. rucio/gateway/heartbeat.py +6 -4
  130. rucio/gateway/identity.py +36 -51
  131. rucio/gateway/importer.py +3 -2
  132. rucio/gateway/lifetime_exception.py +3 -2
  133. rucio/gateway/meta_conventions.py +17 -6
  134. rucio/gateway/permission.py +4 -1
  135. rucio/gateway/quarantined_replica.py +3 -2
  136. rucio/gateway/replica.py +31 -22
  137. rucio/gateway/request.py +27 -18
  138. rucio/gateway/rse.py +69 -37
  139. rucio/gateway/rule.py +46 -26
  140. rucio/gateway/scope.py +3 -2
  141. rucio/gateway/subscription.py +14 -11
  142. rucio/gateway/vo.py +12 -8
  143. rucio/rse/__init__.py +3 -3
  144. rucio/rse/protocols/bittorrent.py +11 -1
  145. rucio/rse/protocols/cache.py +0 -11
  146. rucio/rse/protocols/dummy.py +0 -11
  147. rucio/rse/protocols/gfal.py +14 -9
  148. rucio/rse/protocols/globus.py +1 -1
  149. rucio/rse/protocols/http_cache.py +1 -1
  150. rucio/rse/protocols/posix.py +2 -2
  151. rucio/rse/protocols/protocol.py +84 -317
  152. rucio/rse/protocols/rclone.py +2 -1
  153. rucio/rse/protocols/rfio.py +10 -1
  154. rucio/rse/protocols/ssh.py +2 -1
  155. rucio/rse/protocols/storm.py +2 -13
  156. rucio/rse/protocols/webdav.py +74 -30
  157. rucio/rse/protocols/xrootd.py +2 -1
  158. rucio/rse/rsemanager.py +170 -53
  159. rucio/rse/translation.py +260 -0
  160. rucio/tests/common.py +23 -13
  161. rucio/tests/common_server.py +26 -9
  162. rucio/transfertool/bittorrent.py +15 -14
  163. rucio/transfertool/bittorrent_driver.py +5 -7
  164. rucio/transfertool/bittorrent_driver_qbittorrent.py +9 -8
  165. rucio/transfertool/fts3.py +20 -16
  166. rucio/transfertool/mock.py +2 -3
  167. rucio/vcsversion.py +4 -4
  168. rucio/version.py +7 -0
  169. rucio/web/rest/flaskapi/v1/accounts.py +17 -3
  170. rucio/web/rest/flaskapi/v1/auth.py +5 -5
  171. rucio/web/rest/flaskapi/v1/credentials.py +3 -2
  172. rucio/web/rest/flaskapi/v1/dids.py +21 -15
  173. rucio/web/rest/flaskapi/v1/identities.py +33 -9
  174. rucio/web/rest/flaskapi/v1/redirect.py +5 -4
  175. rucio/web/rest/flaskapi/v1/replicas.py +12 -8
  176. rucio/web/rest/flaskapi/v1/rses.py +15 -4
  177. rucio/web/rest/flaskapi/v1/traces.py +56 -19
  178. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/data/rucio/etc/alembic.ini.template +1 -1
  179. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/data/rucio/etc/alembic_offline.ini.template +1 -1
  180. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/data/rucio/etc/rucio.cfg.atlas.client.template +3 -2
  181. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/data/rucio/etc/rucio.cfg.template +3 -19
  182. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/data/rucio/etc/rucio_multi_vo.cfg.template +1 -18
  183. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/data/rucio/requirements.server.txt +97 -68
  184. rucio-37.0.0rc2.data/scripts/rucio +133 -0
  185. rucio-37.0.0rc2.data/scripts/rucio-admin +97 -0
  186. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-atropos +2 -2
  187. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-auditor +2 -1
  188. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-automatix +2 -2
  189. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-cache-client +17 -10
  190. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-conveyor-receiver +1 -0
  191. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-kronos +1 -0
  192. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-minos +2 -2
  193. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-minos-temporary-expiration +2 -2
  194. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-necromancer +2 -2
  195. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-reaper +6 -6
  196. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-transmogrifier +2 -2
  197. rucio-37.0.0rc2.dist-info/METADATA +92 -0
  198. {rucio-35.7.0.dist-info → rucio-37.0.0rc2.dist-info}/RECORD +239 -245
  199. {rucio-35.7.0.dist-info → rucio-37.0.0rc2.dist-info}/licenses/AUTHORS.rst +3 -0
  200. rucio/common/schema/atlas.py +0 -413
  201. rucio/common/schema/belleii.py +0 -408
  202. rucio/common/schema/domatpc.py +0 -401
  203. rucio/common/schema/escape.py +0 -426
  204. rucio/common/schema/icecube.py +0 -406
  205. rucio/core/permission/atlas.py +0 -1348
  206. rucio/core/permission/belleii.py +0 -1077
  207. rucio/core/permission/escape.py +0 -1078
  208. rucio/daemons/c3po/algorithms/__init__.py +0 -13
  209. rucio/daemons/c3po/algorithms/simple.py +0 -134
  210. rucio/daemons/c3po/algorithms/t2_free_space.py +0 -128
  211. rucio/daemons/c3po/algorithms/t2_free_space_only_pop.py +0 -130
  212. rucio/daemons/c3po/algorithms/t2_free_space_only_pop_with_network.py +0 -294
  213. rucio/daemons/c3po/c3po.py +0 -371
  214. rucio/daemons/c3po/collectors/agis.py +0 -108
  215. rucio/daemons/c3po/collectors/free_space.py +0 -81
  216. rucio/daemons/c3po/collectors/jedi_did.py +0 -57
  217. rucio/daemons/c3po/collectors/mock_did.py +0 -51
  218. rucio/daemons/c3po/collectors/network_metrics.py +0 -71
  219. rucio/daemons/c3po/collectors/workload.py +0 -112
  220. rucio/daemons/c3po/utils/__init__.py +0 -13
  221. rucio/daemons/c3po/utils/dataset_cache.py +0 -50
  222. rucio/daemons/c3po/utils/expiring_dataset_cache.py +0 -56
  223. rucio/daemons/c3po/utils/expiring_list.py +0 -62
  224. rucio/daemons/c3po/utils/popularity.py +0 -85
  225. rucio/daemons/c3po/utils/timeseries.py +0 -89
  226. rucio/rse/protocols/gsiftp.py +0 -92
  227. rucio-35.7.0.data/scripts/rucio-c3po +0 -85
  228. rucio-35.7.0.dist-info/METADATA +0 -72
  229. /rucio/{daemons/c3po → cli/bin_legacy}/__init__.py +0 -0
  230. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/data/rucio/etc/globus-config.yml.template +0 -0
  231. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/data/rucio/etc/ldap.cfg.template +0 -0
  232. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/data/rucio/etc/mail_templates/rule_approval_request.tmpl +0 -0
  233. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/data/rucio/etc/mail_templates/rule_approved_admin.tmpl +0 -0
  234. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/data/rucio/etc/mail_templates/rule_approved_user.tmpl +0 -0
  235. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/data/rucio/etc/mail_templates/rule_denied_admin.tmpl +0 -0
  236. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/data/rucio/etc/mail_templates/rule_denied_user.tmpl +0 -0
  237. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/data/rucio/etc/mail_templates/rule_ok_notification.tmpl +0 -0
  238. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/data/rucio/etc/rse-accounts.cfg.template +0 -0
  239. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/data/rucio/tools/bootstrap.py +0 -0
  240. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/data/rucio/tools/merge_rucio_configs.py +0 -0
  241. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/data/rucio/tools/reset_database.py +0 -0
  242. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-abacus-account +0 -0
  243. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-abacus-collection-replica +0 -0
  244. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-abacus-rse +0 -0
  245. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-bb8 +0 -0
  246. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-cache-consumer +0 -0
  247. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-conveyor-finisher +0 -0
  248. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-conveyor-poller +0 -0
  249. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-conveyor-preparer +0 -0
  250. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-conveyor-stager +0 -0
  251. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-conveyor-submitter +0 -0
  252. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-conveyor-throttler +0 -0
  253. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-dark-reaper +0 -0
  254. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-dumper +0 -0
  255. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-follower +0 -0
  256. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-hermes +0 -0
  257. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-judge-cleaner +0 -0
  258. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-judge-evaluator +0 -0
  259. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-judge-injector +0 -0
  260. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-judge-repairer +0 -0
  261. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-oauth-manager +0 -0
  262. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-replica-recoverer +0 -0
  263. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-rse-decommissioner +0 -0
  264. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-storage-consistency-actions +0 -0
  265. {rucio-35.7.0.data → rucio-37.0.0rc2.data}/scripts/rucio-undertaker +0 -0
  266. {rucio-35.7.0.dist-info → rucio-37.0.0rc2.dist-info}/WHEEL +0 -0
  267. {rucio-35.7.0.dist-info → rucio-37.0.0rc2.dist-info}/licenses/LICENSE +0 -0
  268. {rucio-35.7.0.dist-info → rucio-37.0.0rc2.dist-info}/top_level.txt +0 -0
@@ -1,294 +0,0 @@
1
- # Copyright European Organization for Nuclear Research (CERN) since 2012
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
-
15
- import logging
16
- from operator import itemgetter
17
- from typing import TYPE_CHECKING, Any
18
-
19
- from rucio.common.config import config_get, config_get_int
20
- from rucio.common.constants import RseAttr
21
- from rucio.common.exception import DataIdentifierNotFound
22
- from rucio.core.did import get_did
23
- from rucio.core.replica import list_dataset_replicas
24
- from rucio.core.rse import get_rse, get_rse_name, list_rse_attributes
25
- from rucio.core.rse_expression_parser import parse_expression
26
- from rucio.daemons.c3po.collectors.free_space import FreeSpaceCollector
27
- from rucio.daemons.c3po.collectors.network_metrics import NetworkMetricsCollector
28
- from rucio.daemons.c3po.utils.dataset_cache import DatasetCache
29
- from rucio.daemons.c3po.utils.expiring_dataset_cache import ExpiringDatasetCache
30
- from rucio.daemons.c3po.utils.popularity import get_popularity
31
- from rucio.daemons.c3po.utils.timeseries import RedisTimeSeries
32
- from rucio.db.sqla.constants import ReplicaState
33
-
34
- if TYPE_CHECKING:
35
- from rucio.common.types import InternalScope
36
-
37
-
38
- class PlacementAlgorithm:
39
- """
40
- Placement algorithm that focusses on free space on T2 DATADISK RSEs. It incorporates network metrics for placement decisions.
41
- """
42
- def __init__(
43
- self,
44
- datatypes: str,
45
- dest_rse_expr: str,
46
- max_bytes_hour: int,
47
- max_files_hour: int,
48
- max_bytes_hour_rse: int,
49
- max_files_hour_rse: int,
50
- min_popularity: int,
51
- min_recent_requests: int,
52
- max_replicas: int
53
- ):
54
- self._fsc = FreeSpaceCollector()
55
- self._nmc = NetworkMetricsCollector()
56
- self._added_cache = ExpiringDatasetCache(config_get('c3po', 'redis_host'), config_get_int('c3po', 'redis_port'), timeout=86400)
57
- self._dc = DatasetCache(config_get('c3po', 'redis_host'), config_get_int('c3po', 'redis_port'), timeout=86400)
58
- self._added_bytes = RedisTimeSeries(config_get('c3po', 'redis_host'), config_get_int('c3po', 'redis_port'), window=3600, prefix="added_bytes_")
59
- self._added_files = RedisTimeSeries(config_get('c3po', 'redis_host'), config_get_int('c3po', 'redis_port'), window=3600, prefix="added_files_")
60
-
61
- self._datatypes = datatypes.split(',')
62
- self._dest_rse_expr = dest_rse_expr
63
- self._max_bytes_hour = max_bytes_hour
64
- self._max_files_hour = max_files_hour
65
- self._max_bytes_hour_rse = max_bytes_hour_rse
66
- self._max_files_hour_rse = max_files_hour_rse
67
- self._min_popularity = min_popularity
68
- self._min_recent_requests = min_recent_requests
69
- self._max_replicas = max_replicas
70
-
71
- rses = parse_expression(self._dest_rse_expr)
72
-
73
- self._rses = {}
74
- self._sites = {}
75
- for rse in rses:
76
- rse_attrs = list_rse_attributes(rse_id=rse['id'])
77
- rse_attrs['rse_id'] = rse['id']
78
- self._rses[rse['id']] = rse_attrs
79
- self._sites[rse_attrs[RseAttr.SITE]] = rse_attrs
80
-
81
- self._dst_penalties = {}
82
- self._src_penalties = {}
83
-
84
- self._print_params()
85
-
86
- def _print_params(self) -> None:
87
- logging.debug('Parameter Overview:')
88
- logging.debug('Algorithm: t2_free_space_only_pop_with_network')
89
- logging.debug('Datatypes: %s' % ','.join(self._datatypes))
90
- logging.debug('Max bytes/files per hour: %d / %d' % (self._max_bytes_hour, self._max_files_hour))
91
- logging.debug('Max bytes/files per hour per RSE: %d / %d' % (self._max_bytes_hour_rse, self._max_files_hour_rse))
92
- logging.debug('Min recent requests / Min popularity: %d / %d' % (self._min_recent_requests, self._min_popularity))
93
- logging.debug('Max existing replicas: %d' % self._max_replicas)
94
-
95
- def __update_penalties(self) -> None:
96
- for rse_id, penalty in self._dst_penalties.items():
97
- if penalty < 100.0:
98
- self._dst_penalties[rse_id] += 10.0
99
-
100
- for rse_id, penalty in self._src_penalties.items():
101
- if penalty < 100.0:
102
- self._src_penalties[rse_id] += 10.0
103
-
104
- def check_did(self, did: tuple['InternalScope', str]) -> dict[str, Any]:
105
- decision: dict[str, Any] = {'did': '{}:{}'.format(did[0].internal, did[1])}
106
- if (self._added_cache.check_dataset(decision['did'])):
107
- decision['error_reason'] = 'already added replica for this did in the last 24h'
108
- return decision
109
-
110
- if (did[0].external is not None) and (not did[0].external.startswith('data')) and (not did[0].external.startswith('mc')):
111
- decision['error_reason'] = 'not a data or mc dataset'
112
- return decision
113
-
114
- datatype = did[1].split('.')[4].split('_')[0]
115
-
116
- if datatype not in self._datatypes:
117
- decision['error_reason'] = 'wrong datatype'
118
- return decision
119
-
120
- try:
121
- meta = get_did(did[0], did[1])
122
- except DataIdentifierNotFound:
123
- decision['error_reason'] = 'did does not exist'
124
- return decision
125
- if meta['length'] is None:
126
- meta['length'] = 0
127
- if meta['bytes'] is None:
128
- meta['bytes'] = 0
129
- logging.debug('got %s:%s, num_files: %d, bytes: %d' % (did[0], did[1], meta['length'], meta['bytes']))
130
-
131
- decision['length'] = meta['length']
132
- decision['bytes'] = meta['bytes']
133
-
134
- total_added_bytes = sum(self._added_bytes.get_series('total'))
135
- total_added_files = sum(self._added_files.get_series('total'))
136
-
137
- logging.debug("total_added_bytes: %d" % total_added_bytes)
138
- logging.debug("total_added_files: %d" % total_added_files)
139
-
140
- if ((total_added_bytes + meta['bytes']) > self._max_bytes_hour):
141
- decision['error_reason'] = 'above bytes limit of %d bytes' % self._max_bytes_hour
142
- return decision
143
- if ((total_added_files + meta['length']) > self._max_files_hour):
144
- decision['error_reason'] = 'above files limit of %d files' % self._max_files_hour
145
- return decision
146
-
147
- last_accesses = self._dc.get_did(did)
148
- self._dc.add_did(did)
149
-
150
- decision['last_accesses'] = last_accesses
151
-
152
- try:
153
- pop = get_popularity(did)
154
- decision['popularity'] = pop or 0.0
155
- except Exception:
156
- decision['error_reason'] = 'problems connecting to ES'
157
- return decision
158
-
159
- if (last_accesses < self._min_recent_requests) and (decision['popularity'] < self._min_popularity):
160
- decision['error_reason'] = 'did not popular enough'
161
- return decision
162
-
163
- return decision
164
-
165
- def place(self, did: tuple['InternalScope', str]) -> dict[str, Any]:
166
- self.__update_penalties()
167
- self._added_bytes.trim()
168
- self._added_files.trim()
169
-
170
- decision = self.check_did(did)
171
-
172
- if 'error_reason' in decision:
173
- return decision
174
-
175
- meta = get_did(did[0], did[1])
176
- available_reps = {}
177
- reps = list_dataset_replicas(did[0], did[1])
178
- num_reps = 0
179
- space_info = self._fsc.get_rse_space()
180
- max_mbps = 0.0
181
- for rep in reps:
182
- rse_attr = list_rse_attributes(rep['rse_id'])
183
- src_rse_id = rep['rse_id']
184
- if RseAttr.SITE not in rse_attr:
185
- continue
186
-
187
- src_site = rse_attr[RseAttr.SITE]
188
- src_rse_info = get_rse(rse_id=src_rse_id)
189
-
190
- if 'type' not in rse_attr:
191
- continue
192
- if rse_attr['type'] != 'DATADISK':
193
- continue
194
- if not src_rse_info['availability_read']:
195
- continue
196
-
197
- if rep['state'] == ReplicaState.AVAILABLE:
198
- if rep['available_length'] == 0:
199
- continue
200
- net_metrics = {}
201
- net_metrics_type = None
202
- for metric_type in ('fts', 'fax', 'perfsonar', 'dashb'):
203
- net_metrics_type = metric_type
204
- net_metrics = self._nmc.getMbps(src_site, metric_type)
205
- if net_metrics:
206
- break
207
- if len(net_metrics) == 0: # type: ignore
208
- continue
209
- available_reps[src_rse_id] = {}
210
- for dst_site, mbps in net_metrics.items(): # type: ignore
211
- if src_site == dst_site:
212
- continue
213
- if dst_site in self._sites:
214
- if mbps > max_mbps:
215
- max_mbps = mbps
216
- dst_rse_id = self._sites[dst_site]['rse_id']
217
- dst_rse_info = get_rse(rse_id=dst_rse_id)
218
-
219
- if not dst_rse_info['availability_write']:
220
- continue
221
-
222
- site_added_bytes = sum(self._added_bytes.get_series(dst_rse_id))
223
- site_added_files = sum(self._added_files.get_series(dst_rse_id))
224
-
225
- if ((site_added_bytes + meta['bytes']) > self._max_bytes_hour_rse):
226
- continue
227
- if ((site_added_files + meta['length']) > self._max_files_hour_rse):
228
- continue
229
-
230
- queued = self._nmc.getQueuedFiles(src_site, dst_site)
231
-
232
- # logging.debug('queued %s -> %s: %d' % (src_site, dst_site, queued))
233
- if queued > 0:
234
- continue
235
- rse_space = space_info.get(dst_rse_id, {'free': 0, 'total': 1})
236
- if src_rse_id not in self._src_penalties:
237
- self._src_penalties[src_rse_id] = 100.0
238
- src_penalty = self._src_penalties[src_rse_id]
239
- if dst_rse_id not in self._dst_penalties:
240
- self._dst_penalties[dst_rse_id] = 100.0
241
- dst_penalty = self._dst_penalties[dst_rse_id]
242
-
243
- free_space = float(rse_space['free']) / float(rse_space['total']) * 100.0
244
- available_reps[src_rse_id][dst_rse_id] = {'free_space': free_space, 'src_penalty': src_penalty, 'dst_penalty': dst_penalty, 'mbps': float(mbps), 'metrics_type': net_metrics_type}
245
-
246
- num_reps += 1
247
-
248
- # decision['replica_rses'] = available_reps
249
- decision['num_replicas'] = num_reps
250
-
251
- if num_reps >= 5:
252
- decision['error_reason'] = 'more than 4 replicas already exist'
253
- return decision
254
-
255
- src_dst_ratios = []
256
-
257
- if max_mbps == 0.0:
258
- decision['error_reason'] = 'could not find enough network metrics'
259
- return decision
260
-
261
- for src_id, dst_ids in available_reps.items():
262
- for dst_id, metrics in dst_ids.items():
263
- if dst_id in available_reps:
264
- continue
265
- bdw = (metrics['mbps'] / max_mbps) * 100.0
266
- src_penalty = self._src_penalties[src_id]
267
- dst_penalty = self._dst_penalties[dst_id]
268
-
269
- ratio = ((metrics['free_space'] / 4.0) + bdw) * src_penalty * dst_penalty
270
- src_dst_ratios.append((src_id, dst_id, ratio))
271
-
272
- if len(src_dst_ratios) == 0:
273
- decision['error_reason'] = 'found no suitable src/dst for replication'
274
- return decision
275
-
276
- sorted_ratios = sorted(src_dst_ratios, key=itemgetter(2), reverse=True)
277
- logging.debug(sorted_ratios)
278
- destination_rse = sorted_ratios[0][1]
279
- source_rse = sorted_ratios[0][0]
280
- decision['destination_rse'] = get_rse_name(rse_id=destination_rse)
281
- decision['source_rse'] = get_rse_name(rse_id=source_rse)
282
- # decision['rse_ratios'] = src_dst_ratios
283
- self._dst_penalties[destination_rse] = 10.0
284
- self._src_penalties[source_rse] = 10.0
285
-
286
- self._added_cache.add_dataset(decision['did'])
287
-
288
- self._added_bytes.add_point(destination_rse, meta['bytes'])
289
- self._added_files.add_point(destination_rse, meta['length'])
290
-
291
- self._added_bytes.add_point('total', meta['bytes'])
292
- self._added_files.add_point('total', meta['length'])
293
-
294
- return decision
@@ -1,371 +0,0 @@
1
- # Copyright European Organization for Nuclear Research (CERN) since 2012
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
-
15
- '''
16
- Dynamic data placement daemon.
17
- '''
18
-
19
- import logging
20
- from datetime import datetime
21
- from hashlib import md5
22
- from json import dumps
23
- from queue import Queue
24
- from threading import Event, Thread
25
- from time import sleep
26
- from typing import TYPE_CHECKING, Optional
27
- from uuid import uuid4
28
-
29
- from requests import post
30
- from requests.auth import HTTPBasicAuth
31
- from requests.exceptions import RequestException
32
-
33
- import rucio.db.sqla.util
34
- from rucio.client import Client
35
- from rucio.common import exception
36
- from rucio.common.config import config_get, config_get_options
37
- from rucio.common.logging import setup_logging
38
- from rucio.common.types import InternalScope
39
- from rucio.daemons.c3po.collectors.free_space import FreeSpaceCollector
40
- from rucio.daemons.c3po.collectors.jedi_did import JediDIDCollector
41
- from rucio.daemons.c3po.collectors.workload import WorkloadCollector
42
-
43
- if TYPE_CHECKING:
44
- from types import FrameType
45
-
46
- GRACEFUL_STOP = Event()
47
- DAEMON_NAME = 'c3po'
48
-
49
-
50
- def read_free_space(
51
- once: bool = False,
52
- thread: int = 0,
53
- waiting_time: int = 1800,
54
- sleep_time: int = 10
55
- ) -> None:
56
- """
57
- Thread to collect the space usage information for RSEs.
58
- """
59
- free_space_collector = FreeSpaceCollector()
60
- timer = waiting_time
61
- while not GRACEFUL_STOP.is_set():
62
- if timer < waiting_time:
63
- timer += sleep_time
64
- sleep(sleep_time)
65
- continue
66
-
67
- logging.info('collecting free space')
68
- free_space_collector.collect_free_space()
69
- timer = 0
70
-
71
-
72
- def read_workload(
73
- once: bool = False,
74
- thread: int = 0,
75
- waiting_time: int = 1800,
76
- sleep_time: int = 10
77
- ) -> None:
78
- """
79
- Thread to collect the workload information from PanDA.
80
- """
81
- workload_collector = WorkloadCollector()
82
- timer = waiting_time
83
- while not GRACEFUL_STOP.is_set():
84
- if timer < waiting_time:
85
- timer += sleep_time
86
- sleep(sleep_time)
87
- continue
88
-
89
- logging.info('collecting workload')
90
- workload_collector.collect_workload()
91
- timer = 0
92
-
93
-
94
- def print_workload(
95
- once: bool = False,
96
- thread: int = 0,
97
- waiting_time: int = 600,
98
- sleep_time: int = 10
99
- ) -> None:
100
- """
101
- Thread to regularly output the workload to logs for debugging.
102
- """
103
- workload_collector = WorkloadCollector()
104
- timer = waiting_time
105
- while not GRACEFUL_STOP.is_set():
106
- if timer < waiting_time:
107
- timer += sleep_time
108
- sleep(sleep_time)
109
- continue
110
-
111
- logging.info('Number of sites cached %d' % len(workload_collector.get_sites()))
112
- for site in workload_collector.get_sites():
113
- logging.info('%s: %d / %d / %d' % (site, workload_collector.get_cur_jobs(site), workload_collector.get_avg_jobs(site), workload_collector.get_max_jobs(site)))
114
- timer = 0
115
-
116
-
117
- def read_dids(
118
- once: bool = False,
119
- thread: int = 0,
120
- did_collector: Optional[JediDIDCollector] = None,
121
- waiting_time: int = 60,
122
- sleep_time: int = 10
123
- ) -> None:
124
- """
125
- Thread to collect DIDs for the placement algorithm.
126
- """
127
- timer = waiting_time
128
- while not GRACEFUL_STOP.is_set():
129
- if timer < waiting_time:
130
- timer += sleep_time
131
- sleep(sleep_time)
132
- continue
133
-
134
- if did_collector is not None:
135
- did_collector.get_dids()
136
- timer = 0
137
-
138
-
139
- def add_rule(
140
- client: Client,
141
- did: dict[str, str],
142
- src_rse: str,
143
- dst_rse: str
144
- ) -> None:
145
- logging.debug('add rule for %s from %s to %s' % (did, src_rse, dst_rse))
146
- r = client.add_replication_rule([did, ], 1, dst_rse, lifetime=604800, account='c3po', source_replica_expression=src_rse, activity='Data Brokering', asynchronous=True)
147
- logging.debug(r)
148
-
149
-
150
- def place_replica(
151
- did_queue: Queue,
152
- once: bool = False,
153
- thread: int = 0,
154
- waiting_time: int = 100,
155
- dry_run: bool = False,
156
- sampling: bool = False,
157
- algorithms: str = 't2_free_space_only_pop_with_network',
158
- datatypes: str = 'NTUP,DAOD',
159
- dest_rse_expr: str = 'type=DATADISK',
160
- max_bytes_hour: int = 100000000000000,
161
- max_files_hour: int = 100000,
162
- max_bytes_hour_rse: int = 50000000000000,
163
- max_files_hour_rse: int = 10000,
164
- min_popularity: int = 8,
165
- min_recent_requests: int = 5,
166
- max_replicas: int = 5,
167
- sleep_time: int = 10
168
- ) -> None:
169
- """
170
- Thread to run the placement algorithm to decide if and where to put new replicas.
171
- """
172
- try:
173
- c3po_options = config_get_options('c3po')
174
- client = None
175
-
176
- if 'algorithms' in c3po_options:
177
- algorithms = config_get('c3po', 'algorithms')
178
-
179
- algorithms_list = algorithms.split(',')
180
-
181
- if not dry_run:
182
- if len(algorithms_list) != 1:
183
- logging.error('Multiple algorithms are only allowed in dry_run mode')
184
- return
185
- client = Client(auth_type='x509_proxy', account='c3po', creds={'client_proxy': '/opt/rucio/etc/ddmadmin.long.proxy'})
186
-
187
- vo = client.vo
188
- instances = {}
189
- for algorithm in algorithms_list:
190
- module_path = 'rucio.daemons.c3po.algorithms.' + algorithm
191
- module = __import__(module_path, globals(), locals(), ['PlacementAlgorithm'])
192
- instance = module.PlacementAlgorithm(datatypes, dest_rse_expr, max_bytes_hour, max_files_hour, max_bytes_hour_rse, max_files_hour_rse, min_popularity, min_recent_requests, max_replicas)
193
- instances[algorithm] = instance
194
-
195
- params = {
196
- 'dry_run': dry_run,
197
- 'sampling': sampling,
198
- 'datatypes': datatypes,
199
- 'dest_rse_expr': dest_rse_expr,
200
- 'max_bytes_hour': max_bytes_hour,
201
- 'max_files_hour': max_files_hour,
202
- 'max_bytes_hour_rse': max_bytes_hour_rse,
203
- 'max_files_hour_rse': max_files_hour_rse,
204
- 'min_recent_requests': min_recent_requests,
205
- 'min_popularity': min_popularity
206
- }
207
-
208
- instance_id = str(uuid4()).split('-')[0]
209
-
210
- elastic_url = config_get('c3po', 'elastic_url')
211
- elastic_index = config_get('c3po', 'elastic_index')
212
-
213
- ca_cert = False
214
- if 'ca_cert' in c3po_options:
215
- ca_cert = config_get('c3po', 'ca_cert')
216
-
217
- auth = False
218
- if ('elastic_user' in c3po_options) and ('elastic_pass' in c3po_options):
219
- auth = HTTPBasicAuth(config_get('c3po', 'elastic_user'), config_get('c3po', 'elastic_pass'))
220
-
221
- w = waiting_time
222
- while not GRACEFUL_STOP.is_set():
223
- if w < waiting_time:
224
- w += sleep_time
225
- sleep(sleep_time)
226
- continue
227
- len_dids = did_queue.qsize()
228
-
229
- if len_dids > 0:
230
- logging.debug('(%s) %d did(s) in queue' % (instance_id, len_dids))
231
- else:
232
- logging.debug('(%s) no dids in queue' % (instance_id))
233
-
234
- for _ in range(0, len_dids):
235
- did = did_queue.get()
236
- if isinstance(did[0], str):
237
- did[0] = InternalScope(did[0], vo=vo)
238
- for algorithm, instance in instances.items():
239
- logging.info('(%s:%s) Retrieved %s:%s from queue. Run placement algorithm' % (algorithm, instance_id, did[0], did[1]))
240
- decision = instance.place(did)
241
- decision['@timestamp'] = datetime.utcnow().isoformat()
242
- decision['algorithm'] = algorithm
243
- decision['instance_id'] = instance_id
244
- decision['params'] = params
245
-
246
- create_rule = True
247
- if sampling and 'error_reason' not in decision:
248
- create_rule = bool(ord(md5(decision['did']).hexdigest()[-1]) & 1)
249
- decision['create_rule'] = create_rule
250
- # write the output to ES for further analysis
251
- index_url = elastic_url + '/' + elastic_index + '-' + datetime.utcnow().strftime('%Y-%m') + '/record/'
252
- try:
253
- if ca_cert:
254
- r = post(index_url, data=dumps(decision), verify=ca_cert, auth=auth)
255
- else:
256
- r = post(index_url, data=dumps(decision))
257
- if r.status_code != 201:
258
- logging.error(r)
259
- logging.error('(%s:%s) could not write to ElasticSearch' % (algorithm, instance_id))
260
- except RequestException as e:
261
- logging.error('(%s:%s) could not write to ElasticSearch' % (algorithm, instance_id))
262
- logging.error(e)
263
- continue
264
-
265
- logging.debug(decision)
266
- if 'error_reason' in decision:
267
- logging.error('(%s:%s) The placement algorithm ran into an error: %s' % (algorithm, instance_id, decision['error_reason']))
268
- continue
269
-
270
- logging.info('(%s:%s) Decided to place a new replica for %s on %s' % (algorithm, instance_id, decision['did'], decision['destination_rse']))
271
-
272
- if (not dry_run) and create_rule:
273
- # DO IT!
274
- try:
275
- add_rule(client, {'scope': did[0].external, 'name': did[1]}, decision.get('source_rse'), decision.get('destination_rse')) # type: ignore
276
- except exception.RucioException as e:
277
- logging.debug(e)
278
-
279
- w = 0
280
- except Exception as e:
281
- logging.critical(e)
282
-
283
-
284
- def stop(signum: Optional[int] = None, frame: Optional['FrameType'] = None) -> None:
285
- """
286
- Graceful exit.
287
- """
288
- GRACEFUL_STOP.set()
289
-
290
-
291
- def run(
292
- once: bool = False,
293
- threads: int = 1,
294
- only_workload: bool = False,
295
- dry_run: bool = False,
296
- sampling: bool = False,
297
- algorithms: str = 't2_free_space_only_pop_with_network',
298
- datatypes: str = 'NTUP,DAOD',
299
- dest_rse_expr: str = 'type=DATADISK',
300
- max_bytes_hour: int = 100000000000000,
301
- max_files_hour: int = 100000,
302
- max_bytes_hour_rse: int = 50000000000000,
303
- max_files_hour_rse: int = 10000,
304
- min_popularity: int = 8,
305
- min_recent_requests: int = 5,
306
- max_replicas: int = 5,
307
- waiting_time_read_free_space: int = 1800,
308
- waiting_time_read_workload: int = 1800,
309
- waiting_time_print_workload: int = 600,
310
- waiting_time_read_dids: int = 60,
311
- waiting_time_place_replica: int = 100,
312
- sleep_time: int = 10
313
- ) -> None:
314
- """
315
- Starts up the main thread
316
- """
317
- setup_logging(process_name=DAEMON_NAME)
318
-
319
- if rucio.db.sqla.util.is_old_db():
320
- raise exception.DatabaseException('Database was not updated, daemon won\'t start')
321
-
322
- logging.info('activating C-3PO')
323
-
324
- thread_list = []
325
- try:
326
- if only_workload:
327
- logging.info('running in workload-collector-only mode')
328
- thread_list.append(Thread(target=read_workload, name='read_workload', kwargs={'thread': 0,
329
- 'waiting_time': waiting_time_read_workload,
330
- 'sleep_time': sleep_time}))
331
- thread_list.append(Thread(target=print_workload, name='print_workload', kwargs={'thread': 0,
332
- 'waiting_time': waiting_time_print_workload,
333
- 'sleep_time': sleep_time}))
334
- else:
335
- logging.info('running in placement mode')
336
- did_queue = Queue()
337
- dc = JediDIDCollector(did_queue)
338
-
339
- thread_list.append(Thread(target=read_free_space, name='read_free_space', kwargs={'thread': 0,
340
- 'waiting_time': waiting_time_read_free_space,
341
- 'sleep_time': sleep_time}))
342
- thread_list.append(Thread(target=read_dids, name='read_dids', kwargs={'thread': 0,
343
- 'did_collector': dc,
344
- 'waiting_time': waiting_time_read_dids,
345
- 'sleep_time': sleep_time}))
346
- thread_list.append(Thread(target=place_replica, name='place_replica', kwargs={'thread': 0,
347
- 'did_queue': did_queue,
348
- 'waiting_time': waiting_time_place_replica,
349
- 'algorithms': algorithms,
350
- 'dry_run': dry_run,
351
- 'sampling': sampling,
352
- 'datatypes': datatypes,
353
- 'dest_rse_expr': dest_rse_expr,
354
- 'max_bytes_hour': max_bytes_hour,
355
- 'max_files_hour': max_files_hour,
356
- 'max_bytes_hour_rse': max_bytes_hour_rse,
357
- 'max_files_hour_rse': max_files_hour_rse,
358
- 'min_popularity': min_popularity,
359
- 'min_recent_requests': min_recent_requests,
360
- 'max_replicas': max_replicas,
361
- 'sleep_time': sleep_time}))
362
-
363
- for t in thread_list:
364
- t.start()
365
-
366
- logging.info('waiting for interrupts')
367
-
368
- while len(thread_list) > 0:
369
- [t.join(timeout=3) for t in thread_list if t and t.is_alive()]
370
- except Exception as error:
371
- logging.critical(error)