rucio 32.8.6__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 (481) hide show
  1. rucio/__init__.py +18 -0
  2. rucio/alembicrevision.py +16 -0
  3. rucio/api/__init__.py +14 -0
  4. rucio/api/account.py +266 -0
  5. rucio/api/account_limit.py +287 -0
  6. rucio/api/authentication.py +302 -0
  7. rucio/api/config.py +218 -0
  8. rucio/api/credential.py +60 -0
  9. rucio/api/did.py +726 -0
  10. rucio/api/dirac.py +71 -0
  11. rucio/api/exporter.py +60 -0
  12. rucio/api/heartbeat.py +62 -0
  13. rucio/api/identity.py +160 -0
  14. rucio/api/importer.py +46 -0
  15. rucio/api/lifetime_exception.py +95 -0
  16. rucio/api/lock.py +131 -0
  17. rucio/api/meta.py +85 -0
  18. rucio/api/permission.py +72 -0
  19. rucio/api/quarantined_replica.py +69 -0
  20. rucio/api/replica.py +528 -0
  21. rucio/api/request.py +220 -0
  22. rucio/api/rse.py +601 -0
  23. rucio/api/rule.py +335 -0
  24. rucio/api/scope.py +89 -0
  25. rucio/api/subscription.py +255 -0
  26. rucio/api/temporary_did.py +49 -0
  27. rucio/api/vo.py +112 -0
  28. rucio/client/__init__.py +16 -0
  29. rucio/client/accountclient.py +413 -0
  30. rucio/client/accountlimitclient.py +155 -0
  31. rucio/client/baseclient.py +929 -0
  32. rucio/client/client.py +77 -0
  33. rucio/client/configclient.py +113 -0
  34. rucio/client/credentialclient.py +54 -0
  35. rucio/client/didclient.py +691 -0
  36. rucio/client/diracclient.py +48 -0
  37. rucio/client/downloadclient.py +1674 -0
  38. rucio/client/exportclient.py +44 -0
  39. rucio/client/fileclient.py +51 -0
  40. rucio/client/importclient.py +42 -0
  41. rucio/client/lifetimeclient.py +74 -0
  42. rucio/client/lockclient.py +99 -0
  43. rucio/client/metaclient.py +137 -0
  44. rucio/client/pingclient.py +45 -0
  45. rucio/client/replicaclient.py +444 -0
  46. rucio/client/requestclient.py +109 -0
  47. rucio/client/rseclient.py +664 -0
  48. rucio/client/ruleclient.py +287 -0
  49. rucio/client/scopeclient.py +88 -0
  50. rucio/client/subscriptionclient.py +161 -0
  51. rucio/client/touchclient.py +78 -0
  52. rucio/client/uploadclient.py +871 -0
  53. rucio/common/__init__.py +14 -0
  54. rucio/common/cache.py +74 -0
  55. rucio/common/config.py +796 -0
  56. rucio/common/constants.py +92 -0
  57. rucio/common/constraints.py +18 -0
  58. rucio/common/didtype.py +187 -0
  59. rucio/common/dumper/__init__.py +306 -0
  60. rucio/common/dumper/consistency.py +449 -0
  61. rucio/common/dumper/data_models.py +325 -0
  62. rucio/common/dumper/path_parsing.py +65 -0
  63. rucio/common/exception.py +1092 -0
  64. rucio/common/extra.py +37 -0
  65. rucio/common/logging.py +404 -0
  66. rucio/common/pcache.py +1387 -0
  67. rucio/common/policy.py +84 -0
  68. rucio/common/schema/__init__.py +143 -0
  69. rucio/common/schema/atlas.py +411 -0
  70. rucio/common/schema/belleii.py +406 -0
  71. rucio/common/schema/cms.py +478 -0
  72. rucio/common/schema/domatpc.py +399 -0
  73. rucio/common/schema/escape.py +424 -0
  74. rucio/common/schema/generic.py +431 -0
  75. rucio/common/schema/generic_multi_vo.py +410 -0
  76. rucio/common/schema/icecube.py +404 -0
  77. rucio/common/schema/lsst.py +423 -0
  78. rucio/common/stomp_utils.py +160 -0
  79. rucio/common/stopwatch.py +56 -0
  80. rucio/common/test_rucio_server.py +148 -0
  81. rucio/common/types.py +158 -0
  82. rucio/common/utils.py +1946 -0
  83. rucio/core/__init__.py +14 -0
  84. rucio/core/account.py +426 -0
  85. rucio/core/account_counter.py +171 -0
  86. rucio/core/account_limit.py +357 -0
  87. rucio/core/authentication.py +563 -0
  88. rucio/core/config.py +386 -0
  89. rucio/core/credential.py +218 -0
  90. rucio/core/did.py +3102 -0
  91. rucio/core/did_meta_plugins/__init__.py +250 -0
  92. rucio/core/did_meta_plugins/did_column_meta.py +326 -0
  93. rucio/core/did_meta_plugins/did_meta_plugin_interface.py +116 -0
  94. rucio/core/did_meta_plugins/filter_engine.py +573 -0
  95. rucio/core/did_meta_plugins/json_meta.py +215 -0
  96. rucio/core/did_meta_plugins/mongo_meta.py +199 -0
  97. rucio/core/did_meta_plugins/postgres_meta.py +317 -0
  98. rucio/core/dirac.py +208 -0
  99. rucio/core/distance.py +164 -0
  100. rucio/core/exporter.py +59 -0
  101. rucio/core/heartbeat.py +263 -0
  102. rucio/core/identity.py +290 -0
  103. rucio/core/importer.py +248 -0
  104. rucio/core/lifetime_exception.py +377 -0
  105. rucio/core/lock.py +474 -0
  106. rucio/core/message.py +241 -0
  107. rucio/core/meta.py +190 -0
  108. rucio/core/monitor.py +441 -0
  109. rucio/core/naming_convention.py +154 -0
  110. rucio/core/nongrid_trace.py +124 -0
  111. rucio/core/oidc.py +1339 -0
  112. rucio/core/permission/__init__.py +107 -0
  113. rucio/core/permission/atlas.py +1333 -0
  114. rucio/core/permission/belleii.py +1076 -0
  115. rucio/core/permission/cms.py +1166 -0
  116. rucio/core/permission/escape.py +1076 -0
  117. rucio/core/permission/generic.py +1128 -0
  118. rucio/core/permission/generic_multi_vo.py +1148 -0
  119. rucio/core/quarantined_replica.py +190 -0
  120. rucio/core/replica.py +3627 -0
  121. rucio/core/replica_sorter.py +368 -0
  122. rucio/core/request.py +2241 -0
  123. rucio/core/rse.py +1835 -0
  124. rucio/core/rse_counter.py +155 -0
  125. rucio/core/rse_expression_parser.py +460 -0
  126. rucio/core/rse_selector.py +277 -0
  127. rucio/core/rule.py +3419 -0
  128. rucio/core/rule_grouping.py +1473 -0
  129. rucio/core/scope.py +152 -0
  130. rucio/core/subscription.py +316 -0
  131. rucio/core/temporary_did.py +188 -0
  132. rucio/core/topology.py +448 -0
  133. rucio/core/trace.py +361 -0
  134. rucio/core/transfer.py +1233 -0
  135. rucio/core/vo.py +151 -0
  136. rucio/core/volatile_replica.py +123 -0
  137. rucio/daemons/__init__.py +14 -0
  138. rucio/daemons/abacus/__init__.py +14 -0
  139. rucio/daemons/abacus/account.py +106 -0
  140. rucio/daemons/abacus/collection_replica.py +113 -0
  141. rucio/daemons/abacus/rse.py +107 -0
  142. rucio/daemons/atropos/__init__.py +14 -0
  143. rucio/daemons/atropos/atropos.py +243 -0
  144. rucio/daemons/auditor/__init__.py +261 -0
  145. rucio/daemons/auditor/hdfs.py +86 -0
  146. rucio/daemons/auditor/srmdumps.py +284 -0
  147. rucio/daemons/automatix/__init__.py +14 -0
  148. rucio/daemons/automatix/automatix.py +281 -0
  149. rucio/daemons/badreplicas/__init__.py +14 -0
  150. rucio/daemons/badreplicas/minos.py +311 -0
  151. rucio/daemons/badreplicas/minos_temporary_expiration.py +173 -0
  152. rucio/daemons/badreplicas/necromancer.py +200 -0
  153. rucio/daemons/bb8/__init__.py +14 -0
  154. rucio/daemons/bb8/bb8.py +356 -0
  155. rucio/daemons/bb8/common.py +762 -0
  156. rucio/daemons/bb8/nuclei_background_rebalance.py +147 -0
  157. rucio/daemons/bb8/t2_background_rebalance.py +146 -0
  158. rucio/daemons/c3po/__init__.py +14 -0
  159. rucio/daemons/c3po/algorithms/__init__.py +14 -0
  160. rucio/daemons/c3po/algorithms/simple.py +131 -0
  161. rucio/daemons/c3po/algorithms/t2_free_space.py +125 -0
  162. rucio/daemons/c3po/algorithms/t2_free_space_only_pop.py +127 -0
  163. rucio/daemons/c3po/algorithms/t2_free_space_only_pop_with_network.py +279 -0
  164. rucio/daemons/c3po/c3po.py +342 -0
  165. rucio/daemons/c3po/collectors/__init__.py +14 -0
  166. rucio/daemons/c3po/collectors/agis.py +108 -0
  167. rucio/daemons/c3po/collectors/free_space.py +62 -0
  168. rucio/daemons/c3po/collectors/jedi_did.py +48 -0
  169. rucio/daemons/c3po/collectors/mock_did.py +46 -0
  170. rucio/daemons/c3po/collectors/network_metrics.py +63 -0
  171. rucio/daemons/c3po/collectors/workload.py +110 -0
  172. rucio/daemons/c3po/utils/__init__.py +14 -0
  173. rucio/daemons/c3po/utils/dataset_cache.py +40 -0
  174. rucio/daemons/c3po/utils/expiring_dataset_cache.py +45 -0
  175. rucio/daemons/c3po/utils/expiring_list.py +63 -0
  176. rucio/daemons/c3po/utils/popularity.py +82 -0
  177. rucio/daemons/c3po/utils/timeseries.py +76 -0
  178. rucio/daemons/cache/__init__.py +14 -0
  179. rucio/daemons/cache/consumer.py +191 -0
  180. rucio/daemons/common.py +391 -0
  181. rucio/daemons/conveyor/__init__.py +14 -0
  182. rucio/daemons/conveyor/common.py +530 -0
  183. rucio/daemons/conveyor/finisher.py +492 -0
  184. rucio/daemons/conveyor/poller.py +372 -0
  185. rucio/daemons/conveyor/preparer.py +198 -0
  186. rucio/daemons/conveyor/receiver.py +206 -0
  187. rucio/daemons/conveyor/stager.py +127 -0
  188. rucio/daemons/conveyor/submitter.py +379 -0
  189. rucio/daemons/conveyor/throttler.py +468 -0
  190. rucio/daemons/follower/__init__.py +14 -0
  191. rucio/daemons/follower/follower.py +97 -0
  192. rucio/daemons/hermes/__init__.py +14 -0
  193. rucio/daemons/hermes/hermes.py +738 -0
  194. rucio/daemons/judge/__init__.py +14 -0
  195. rucio/daemons/judge/cleaner.py +149 -0
  196. rucio/daemons/judge/evaluator.py +172 -0
  197. rucio/daemons/judge/injector.py +154 -0
  198. rucio/daemons/judge/repairer.py +144 -0
  199. rucio/daemons/oauthmanager/__init__.py +14 -0
  200. rucio/daemons/oauthmanager/oauthmanager.py +199 -0
  201. rucio/daemons/reaper/__init__.py +14 -0
  202. rucio/daemons/reaper/dark_reaper.py +272 -0
  203. rucio/daemons/reaper/light_reaper.py +255 -0
  204. rucio/daemons/reaper/reaper.py +701 -0
  205. rucio/daemons/replicarecoverer/__init__.py +14 -0
  206. rucio/daemons/replicarecoverer/suspicious_replica_recoverer.py +487 -0
  207. rucio/daemons/storage/__init__.py +14 -0
  208. rucio/daemons/storage/consistency/__init__.py +14 -0
  209. rucio/daemons/storage/consistency/actions.py +753 -0
  210. rucio/daemons/tracer/__init__.py +14 -0
  211. rucio/daemons/tracer/kronos.py +513 -0
  212. rucio/daemons/transmogrifier/__init__.py +14 -0
  213. rucio/daemons/transmogrifier/transmogrifier.py +753 -0
  214. rucio/daemons/undertaker/__init__.py +14 -0
  215. rucio/daemons/undertaker/undertaker.py +137 -0
  216. rucio/db/__init__.py +14 -0
  217. rucio/db/sqla/__init__.py +38 -0
  218. rucio/db/sqla/constants.py +192 -0
  219. rucio/db/sqla/migrate_repo/__init__.py +14 -0
  220. rucio/db/sqla/migrate_repo/env.py +111 -0
  221. rucio/db/sqla/migrate_repo/versions/01eaf73ab656_add_new_rule_notification_state_progress.py +71 -0
  222. rucio/db/sqla/migrate_repo/versions/0437a40dbfd1_add_eol_at_in_rules.py +50 -0
  223. rucio/db/sqla/migrate_repo/versions/0f1adb7a599a_create_transfer_hops_table.py +61 -0
  224. rucio/db/sqla/migrate_repo/versions/102efcf145f4_added_stuck_at_column_to_rules.py +46 -0
  225. rucio/db/sqla/migrate_repo/versions/13d4f70c66a9_introduce_transfer_limits.py +93 -0
  226. rucio/db/sqla/migrate_repo/versions/140fef722e91_cleanup_distances_table.py +78 -0
  227. rucio/db/sqla/migrate_repo/versions/14ec5aeb64cf_add_request_external_host.py +46 -0
  228. rucio/db/sqla/migrate_repo/versions/156fb5b5a14_add_request_type_to_requests_idx.py +53 -0
  229. rucio/db/sqla/migrate_repo/versions/1677d4d803c8_split_rse_availability_into_multiple.py +69 -0
  230. rucio/db/sqla/migrate_repo/versions/16a0aca82e12_create_index_on_table_replicas_path.py +42 -0
  231. rucio/db/sqla/migrate_repo/versions/1803333ac20f_adding_provenance_and_phys_group.py +46 -0
  232. rucio/db/sqla/migrate_repo/versions/1a29d6a9504c_add_didtype_chck_to_requests.py +61 -0
  233. rucio/db/sqla/migrate_repo/versions/1a80adff031a_create_index_on_rules_hist_recent.py +42 -0
  234. rucio/db/sqla/migrate_repo/versions/1c45d9730ca6_increase_identity_length.py +141 -0
  235. rucio/db/sqla/migrate_repo/versions/1d1215494e95_add_quarantined_replicas_table.py +75 -0
  236. rucio/db/sqla/migrate_repo/versions/1d96f484df21_asynchronous_rules_and_rule_approval.py +75 -0
  237. rucio/db/sqla/migrate_repo/versions/1f46c5f240ac_add_bytes_column_to_bad_replicas.py +46 -0
  238. rucio/db/sqla/migrate_repo/versions/1fc15ab60d43_add_message_history_table.py +51 -0
  239. rucio/db/sqla/migrate_repo/versions/2190e703eb6e_move_rse_settings_to_rse_attributes.py +135 -0
  240. rucio/db/sqla/migrate_repo/versions/21d6b9dc9961_add_mismatch_scheme_state_to_requests.py +65 -0
  241. rucio/db/sqla/migrate_repo/versions/22cf51430c78_add_availability_column_to_table_rses.py +42 -0
  242. rucio/db/sqla/migrate_repo/versions/22d887e4ec0a_create_sources_table.py +66 -0
  243. rucio/db/sqla/migrate_repo/versions/25821a8a45a3_remove_unique_constraint_on_requests.py +54 -0
  244. rucio/db/sqla/migrate_repo/versions/25fc855625cf_added_unique_constraint_to_rules.py +43 -0
  245. rucio/db/sqla/migrate_repo/versions/269fee20dee9_add_repair_cnt_to_locks.py +46 -0
  246. rucio/db/sqla/migrate_repo/versions/271a46ea6244_add_ignore_availability_column_to_rules.py +47 -0
  247. rucio/db/sqla/migrate_repo/versions/277b5fbb41d3_switch_heartbeats_executable.py +54 -0
  248. rucio/db/sqla/migrate_repo/versions/27e3a68927fb_remove_replicas_tombstone_and_replicas_.py +39 -0
  249. rucio/db/sqla/migrate_repo/versions/2854cd9e168_added_rule_id_column.py +48 -0
  250. rucio/db/sqla/migrate_repo/versions/295289b5a800_processed_by_and__at_in_requests.py +47 -0
  251. rucio/db/sqla/migrate_repo/versions/2962ece31cf4_add_nbaccesses_column_in_the_did_table.py +48 -0
  252. rucio/db/sqla/migrate_repo/versions/2af3291ec4c_added_replicas_history_table.py +59 -0
  253. rucio/db/sqla/migrate_repo/versions/2b69addda658_add_columns_for_third_party_copy_read_.py +47 -0
  254. rucio/db/sqla/migrate_repo/versions/2b8e7bcb4783_add_config_table.py +72 -0
  255. rucio/db/sqla/migrate_repo/versions/2ba5229cb54c_add_submitted_at_to_requests_table.py +46 -0
  256. rucio/db/sqla/migrate_repo/versions/2cbee484dcf9_added_column_volume_to_rse_transfer_.py +45 -0
  257. rucio/db/sqla/migrate_repo/versions/2edee4a83846_add_source_to_requests_and_requests_.py +48 -0
  258. rucio/db/sqla/migrate_repo/versions/2eef46be23d4_change_tokens_pk.py +48 -0
  259. rucio/db/sqla/migrate_repo/versions/2f648fc909f3_index_in_rule_history_on_scope_name.py +42 -0
  260. rucio/db/sqla/migrate_repo/versions/3082b8cef557_add_naming_convention_table_and_closed_.py +69 -0
  261. rucio/db/sqla/migrate_repo/versions/30fa38b6434e_add_index_on_service_column_in_the_message_table.py +46 -0
  262. rucio/db/sqla/migrate_repo/versions/3152492b110b_added_staging_area_column.py +78 -0
  263. rucio/db/sqla/migrate_repo/versions/32c7d2783f7e_create_bad_replicas_table.py +62 -0
  264. rucio/db/sqla/migrate_repo/versions/3345511706b8_replicas_table_pk_definition_is_in_.py +74 -0
  265. rucio/db/sqla/migrate_repo/versions/35ef10d1e11b_change_index_on_table_requests.py +44 -0
  266. rucio/db/sqla/migrate_repo/versions/379a19b5332d_create_rse_limits_table.py +67 -0
  267. rucio/db/sqla/migrate_repo/versions/384b96aa0f60_created_rule_history_tables.py +134 -0
  268. rucio/db/sqla/migrate_repo/versions/3ac1660a1a72_extend_distance_table.py +58 -0
  269. rucio/db/sqla/migrate_repo/versions/3ad36e2268b0_create_collection_replicas_updates_table.py +79 -0
  270. rucio/db/sqla/migrate_repo/versions/3c9df354071b_extend_waiting_request_state.py +61 -0
  271. rucio/db/sqla/migrate_repo/versions/3d9813fab443_add_a_new_state_lost_in_badfilesstatus.py +45 -0
  272. rucio/db/sqla/migrate_repo/versions/40ad39ce3160_add_transferred_at_to_requests_table.py +46 -0
  273. rucio/db/sqla/migrate_repo/versions/4207be2fd914_add_notification_column_to_rules.py +65 -0
  274. rucio/db/sqla/migrate_repo/versions/42db2617c364_create_index_on_requests_external_id.py +42 -0
  275. rucio/db/sqla/migrate_repo/versions/436827b13f82_added_column_activity_to_table_requests.py +46 -0
  276. rucio/db/sqla/migrate_repo/versions/44278720f774_update_requests_typ_sta_upd_idx_index.py +46 -0
  277. rucio/db/sqla/migrate_repo/versions/45378a1e76a8_create_collection_replica_table.py +80 -0
  278. rucio/db/sqla/migrate_repo/versions/469d262be19_removing_created_at_index.py +43 -0
  279. rucio/db/sqla/migrate_repo/versions/4783c1f49cb4_create_distance_table.py +61 -0
  280. rucio/db/sqla/migrate_repo/versions/49a21b4d4357_create_index_on_table_tokens.py +47 -0
  281. rucio/db/sqla/migrate_repo/versions/4a2cbedda8b9_add_source_replica_expression_column_to_.py +46 -0
  282. rucio/db/sqla/migrate_repo/versions/4a7182d9578b_added_bytes_length_accessed_at_columns.py +52 -0
  283. rucio/db/sqla/migrate_repo/versions/4bab9edd01fc_create_index_on_requests_rule_id.py +42 -0
  284. rucio/db/sqla/migrate_repo/versions/4c3a4acfe006_new_attr_account_table.py +65 -0
  285. rucio/db/sqla/migrate_repo/versions/4cf0a2e127d4_adding_transient_metadata.py +46 -0
  286. rucio/db/sqla/migrate_repo/versions/50280c53117c_add_qos_class_to_rse.py +47 -0
  287. rucio/db/sqla/migrate_repo/versions/52153819589c_add_rse_id_to_replicas_table.py +45 -0
  288. rucio/db/sqla/migrate_repo/versions/52fd9f4916fa_added_activity_to_rules.py +46 -0
  289. rucio/db/sqla/migrate_repo/versions/53b479c3cb0f_fix_did_meta_table_missing_updated_at_.py +48 -0
  290. rucio/db/sqla/migrate_repo/versions/5673b4b6e843_add_wfms_metadata_to_rule_tables.py +50 -0
  291. rucio/db/sqla/migrate_repo/versions/575767d9f89_added_source_history_table.py +59 -0
  292. rucio/db/sqla/migrate_repo/versions/58bff7008037_add_started_at_to_requests.py +48 -0
  293. rucio/db/sqla/migrate_repo/versions/58c8b78301ab_rename_callback_to_message.py +108 -0
  294. rucio/db/sqla/migrate_repo/versions/5f139f77382a_added_child_rule_id_column.py +57 -0
  295. rucio/db/sqla/migrate_repo/versions/688ef1840840_adding_did_meta_table.py +51 -0
  296. rucio/db/sqla/migrate_repo/versions/6e572a9bfbf3_add_new_split_container_column_to_rules.py +50 -0
  297. rucio/db/sqla/migrate_repo/versions/70587619328_add_comment_column_for_subscriptions.py +46 -0
  298. rucio/db/sqla/migrate_repo/versions/739064d31565_remove_history_table_pks.py +42 -0
  299. rucio/db/sqla/migrate_repo/versions/7541902bf173_add_didsfollowed_and_followevents_table.py +93 -0
  300. rucio/db/sqla/migrate_repo/versions/7ec22226cdbf_new_replica_state_for_temporary_.py +73 -0
  301. rucio/db/sqla/migrate_repo/versions/810a41685bc1_added_columns_rse_transfer_limits.py +52 -0
  302. rucio/db/sqla/migrate_repo/versions/83f991c63a93_correct_rse_expression_length.py +45 -0
  303. rucio/db/sqla/migrate_repo/versions/8523998e2e76_increase_size_of_extended_attributes_.py +46 -0
  304. rucio/db/sqla/migrate_repo/versions/8ea9122275b1_adding_missing_function_based_indices.py +54 -0
  305. rucio/db/sqla/migrate_repo/versions/90f47792bb76_add_clob_payload_to_messages.py +48 -0
  306. rucio/db/sqla/migrate_repo/versions/914b8f02df38_new_table_for_lifetime_model_exceptions.py +70 -0
  307. rucio/db/sqla/migrate_repo/versions/94a5961ddbf2_add_estimator_columns.py +48 -0
  308. rucio/db/sqla/migrate_repo/versions/9a1b149a2044_add_saml_identity_type.py +95 -0
  309. rucio/db/sqla/migrate_repo/versions/9a45bc4ea66d_add_vp_table.py +55 -0
  310. rucio/db/sqla/migrate_repo/versions/9eb936a81eb1_true_is_true.py +74 -0
  311. rucio/db/sqla/migrate_repo/versions/a118956323f8_added_vo_table_and_vo_col_to_rse.py +78 -0
  312. rucio/db/sqla/migrate_repo/versions/a193a275255c_add_status_column_in_messages.py +49 -0
  313. rucio/db/sqla/migrate_repo/versions/a5f6f6e928a7_1_7_0.py +124 -0
  314. rucio/db/sqla/migrate_repo/versions/a616581ee47_added_columns_to_table_requests.py +60 -0
  315. rucio/db/sqla/migrate_repo/versions/a6eb23955c28_state_idx_non_functional.py +53 -0
  316. rucio/db/sqla/migrate_repo/versions/a74275a1ad30_added_global_quota_table.py +56 -0
  317. rucio/db/sqla/migrate_repo/versions/a93e4e47bda_heartbeats.py +67 -0
  318. rucio/db/sqla/migrate_repo/versions/ae2a56fcc89_added_comment_column_to_rules.py +50 -0
  319. rucio/db/sqla/migrate_repo/versions/b4293a99f344_added_column_identity_to_table_tokens.py +46 -0
  320. rucio/db/sqla/migrate_repo/versions/b7d287de34fd_removal_of_replicastate_source.py +92 -0
  321. rucio/db/sqla/migrate_repo/versions/b818052fa670_add_index_to_quarantined_replicas.py +42 -0
  322. rucio/db/sqla/migrate_repo/versions/b8caac94d7f0_add_comments_column_for_subscriptions_.py +46 -0
  323. rucio/db/sqla/migrate_repo/versions/b96a1c7e1cc4_new_bad_pfns_table_and_bad_replicas_.py +147 -0
  324. rucio/db/sqla/migrate_repo/versions/bb695f45c04_extend_request_state.py +78 -0
  325. rucio/db/sqla/migrate_repo/versions/bc68e9946deb_add_staging_timestamps_to_request.py +53 -0
  326. rucio/db/sqla/migrate_repo/versions/bf3baa1c1474_correct_pk_and_idx_for_history_tables.py +74 -0
  327. rucio/db/sqla/migrate_repo/versions/c0937668555f_add_qos_policy_map_table.py +56 -0
  328. rucio/db/sqla/migrate_repo/versions/c129ccdb2d5_add_lumiblocknr_to_dids.py +46 -0
  329. rucio/db/sqla/migrate_repo/versions/ccdbcd48206e_add_did_type_column_index_on_did_meta_.py +68 -0
  330. rucio/db/sqla/migrate_repo/versions/cebad904c4dd_new_payload_column_for_heartbeats.py +48 -0
  331. rucio/db/sqla/migrate_repo/versions/d1189a09c6e0_oauth2_0_and_jwt_feature_support_adding_.py +149 -0
  332. rucio/db/sqla/migrate_repo/versions/d23453595260_extend_request_state_for_preparer.py +106 -0
  333. rucio/db/sqla/migrate_repo/versions/d6dceb1de2d_added_purge_column_to_rules.py +47 -0
  334. rucio/db/sqla/migrate_repo/versions/d6e2c3b2cf26_remove_third_party_copy_column_from_rse.py +45 -0
  335. rucio/db/sqla/migrate_repo/versions/d91002c5841_new_account_limits_table.py +105 -0
  336. rucio/db/sqla/migrate_repo/versions/e138c364ebd0_extending_columns_for_filter_and_.py +52 -0
  337. rucio/db/sqla/migrate_repo/versions/e59300c8b179_support_for_archive.py +106 -0
  338. rucio/db/sqla/migrate_repo/versions/f1b14a8c2ac1_postgres_use_check_constraints.py +30 -0
  339. rucio/db/sqla/migrate_repo/versions/f41ffe206f37_oracle_global_temporary_tables.py +75 -0
  340. rucio/db/sqla/migrate_repo/versions/f85a2962b021_adding_transfertool_column_to_requests_.py +49 -0
  341. rucio/db/sqla/migrate_repo/versions/fa7a7d78b602_increase_refresh_token_size.py +45 -0
  342. rucio/db/sqla/migrate_repo/versions/fb28a95fe288_add_replicas_rse_id_tombstone_idx.py +38 -0
  343. rucio/db/sqla/migrate_repo/versions/fe1a65b176c9_set_third_party_copy_read_and_write_.py +44 -0
  344. rucio/db/sqla/migrate_repo/versions/fe8ea2fa9788_added_third_party_copy_column_to_rse_.py +46 -0
  345. rucio/db/sqla/models.py +1834 -0
  346. rucio/db/sqla/sautils.py +48 -0
  347. rucio/db/sqla/session.py +470 -0
  348. rucio/db/sqla/types.py +207 -0
  349. rucio/db/sqla/util.py +521 -0
  350. rucio/rse/__init__.py +97 -0
  351. rucio/rse/protocols/__init__.py +14 -0
  352. rucio/rse/protocols/cache.py +123 -0
  353. rucio/rse/protocols/dummy.py +112 -0
  354. rucio/rse/protocols/gfal.py +701 -0
  355. rucio/rse/protocols/globus.py +243 -0
  356. rucio/rse/protocols/gsiftp.py +93 -0
  357. rucio/rse/protocols/http_cache.py +83 -0
  358. rucio/rse/protocols/mock.py +124 -0
  359. rucio/rse/protocols/ngarc.py +210 -0
  360. rucio/rse/protocols/posix.py +251 -0
  361. rucio/rse/protocols/protocol.py +530 -0
  362. rucio/rse/protocols/rclone.py +365 -0
  363. rucio/rse/protocols/rfio.py +137 -0
  364. rucio/rse/protocols/srm.py +339 -0
  365. rucio/rse/protocols/ssh.py +414 -0
  366. rucio/rse/protocols/storm.py +207 -0
  367. rucio/rse/protocols/webdav.py +547 -0
  368. rucio/rse/protocols/xrootd.py +295 -0
  369. rucio/rse/rsemanager.py +752 -0
  370. rucio/tests/__init__.py +14 -0
  371. rucio/tests/common.py +244 -0
  372. rucio/tests/common_server.py +132 -0
  373. rucio/transfertool/__init__.py +14 -0
  374. rucio/transfertool/fts3.py +1484 -0
  375. rucio/transfertool/globus.py +200 -0
  376. rucio/transfertool/globus_library.py +182 -0
  377. rucio/transfertool/mock.py +81 -0
  378. rucio/transfertool/transfertool.py +212 -0
  379. rucio/vcsversion.py +11 -0
  380. rucio/version.py +46 -0
  381. rucio/web/__init__.py +14 -0
  382. rucio/web/rest/__init__.py +14 -0
  383. rucio/web/rest/flaskapi/__init__.py +14 -0
  384. rucio/web/rest/flaskapi/authenticated_bp.py +28 -0
  385. rucio/web/rest/flaskapi/v1/__init__.py +14 -0
  386. rucio/web/rest/flaskapi/v1/accountlimits.py +234 -0
  387. rucio/web/rest/flaskapi/v1/accounts.py +1088 -0
  388. rucio/web/rest/flaskapi/v1/archives.py +100 -0
  389. rucio/web/rest/flaskapi/v1/auth.py +1642 -0
  390. rucio/web/rest/flaskapi/v1/common.py +385 -0
  391. rucio/web/rest/flaskapi/v1/config.py +305 -0
  392. rucio/web/rest/flaskapi/v1/credentials.py +213 -0
  393. rucio/web/rest/flaskapi/v1/dids.py +2204 -0
  394. rucio/web/rest/flaskapi/v1/dirac.py +116 -0
  395. rucio/web/rest/flaskapi/v1/export.py +77 -0
  396. rucio/web/rest/flaskapi/v1/heartbeats.py +129 -0
  397. rucio/web/rest/flaskapi/v1/identities.py +263 -0
  398. rucio/web/rest/flaskapi/v1/import.py +133 -0
  399. rucio/web/rest/flaskapi/v1/lifetime_exceptions.py +315 -0
  400. rucio/web/rest/flaskapi/v1/locks.py +360 -0
  401. rucio/web/rest/flaskapi/v1/main.py +83 -0
  402. rucio/web/rest/flaskapi/v1/meta.py +226 -0
  403. rucio/web/rest/flaskapi/v1/metrics.py +37 -0
  404. rucio/web/rest/flaskapi/v1/nongrid_traces.py +97 -0
  405. rucio/web/rest/flaskapi/v1/ping.py +89 -0
  406. rucio/web/rest/flaskapi/v1/redirect.py +366 -0
  407. rucio/web/rest/flaskapi/v1/replicas.py +1866 -0
  408. rucio/web/rest/flaskapi/v1/requests.py +841 -0
  409. rucio/web/rest/flaskapi/v1/rses.py +2204 -0
  410. rucio/web/rest/flaskapi/v1/rules.py +824 -0
  411. rucio/web/rest/flaskapi/v1/scopes.py +161 -0
  412. rucio/web/rest/flaskapi/v1/subscriptions.py +646 -0
  413. rucio/web/rest/flaskapi/v1/templates/auth_crash.html +80 -0
  414. rucio/web/rest/flaskapi/v1/templates/auth_granted.html +82 -0
  415. rucio/web/rest/flaskapi/v1/tmp_dids.py +115 -0
  416. rucio/web/rest/flaskapi/v1/traces.py +100 -0
  417. rucio/web/rest/flaskapi/v1/vos.py +280 -0
  418. rucio/web/rest/main.py +19 -0
  419. rucio/web/rest/metrics.py +28 -0
  420. rucio-32.8.6.data/data/rucio/etc/alembic.ini.template +71 -0
  421. rucio-32.8.6.data/data/rucio/etc/alembic_offline.ini.template +74 -0
  422. rucio-32.8.6.data/data/rucio/etc/globus-config.yml.template +5 -0
  423. rucio-32.8.6.data/data/rucio/etc/ldap.cfg.template +30 -0
  424. rucio-32.8.6.data/data/rucio/etc/mail_templates/rule_approval_request.tmpl +38 -0
  425. rucio-32.8.6.data/data/rucio/etc/mail_templates/rule_approved_admin.tmpl +4 -0
  426. rucio-32.8.6.data/data/rucio/etc/mail_templates/rule_approved_user.tmpl +17 -0
  427. rucio-32.8.6.data/data/rucio/etc/mail_templates/rule_denied_admin.tmpl +6 -0
  428. rucio-32.8.6.data/data/rucio/etc/mail_templates/rule_denied_user.tmpl +17 -0
  429. rucio-32.8.6.data/data/rucio/etc/mail_templates/rule_ok_notification.tmpl +19 -0
  430. rucio-32.8.6.data/data/rucio/etc/rse-accounts.cfg.template +25 -0
  431. rucio-32.8.6.data/data/rucio/etc/rucio.cfg.atlas.client.template +42 -0
  432. rucio-32.8.6.data/data/rucio/etc/rucio.cfg.template +257 -0
  433. rucio-32.8.6.data/data/rucio/etc/rucio_multi_vo.cfg.template +234 -0
  434. rucio-32.8.6.data/data/rucio/requirements.txt +55 -0
  435. rucio-32.8.6.data/data/rucio/tools/bootstrap.py +34 -0
  436. rucio-32.8.6.data/data/rucio/tools/merge_rucio_configs.py +147 -0
  437. rucio-32.8.6.data/data/rucio/tools/reset_database.py +40 -0
  438. rucio-32.8.6.data/scripts/rucio +2540 -0
  439. rucio-32.8.6.data/scripts/rucio-abacus-account +75 -0
  440. rucio-32.8.6.data/scripts/rucio-abacus-collection-replica +47 -0
  441. rucio-32.8.6.data/scripts/rucio-abacus-rse +79 -0
  442. rucio-32.8.6.data/scripts/rucio-admin +2434 -0
  443. rucio-32.8.6.data/scripts/rucio-atropos +61 -0
  444. rucio-32.8.6.data/scripts/rucio-auditor +199 -0
  445. rucio-32.8.6.data/scripts/rucio-automatix +51 -0
  446. rucio-32.8.6.data/scripts/rucio-bb8 +58 -0
  447. rucio-32.8.6.data/scripts/rucio-c3po +86 -0
  448. rucio-32.8.6.data/scripts/rucio-cache-client +135 -0
  449. rucio-32.8.6.data/scripts/rucio-cache-consumer +43 -0
  450. rucio-32.8.6.data/scripts/rucio-conveyor-finisher +59 -0
  451. rucio-32.8.6.data/scripts/rucio-conveyor-poller +67 -0
  452. rucio-32.8.6.data/scripts/rucio-conveyor-preparer +38 -0
  453. rucio-32.8.6.data/scripts/rucio-conveyor-receiver +44 -0
  454. rucio-32.8.6.data/scripts/rucio-conveyor-stager +77 -0
  455. rucio-32.8.6.data/scripts/rucio-conveyor-submitter +140 -0
  456. rucio-32.8.6.data/scripts/rucio-conveyor-throttler +105 -0
  457. rucio-32.8.6.data/scripts/rucio-dark-reaper +54 -0
  458. rucio-32.8.6.data/scripts/rucio-dumper +159 -0
  459. rucio-32.8.6.data/scripts/rucio-follower +45 -0
  460. rucio-32.8.6.data/scripts/rucio-hermes +55 -0
  461. rucio-32.8.6.data/scripts/rucio-judge-cleaner +90 -0
  462. rucio-32.8.6.data/scripts/rucio-judge-evaluator +138 -0
  463. rucio-32.8.6.data/scripts/rucio-judge-injector +45 -0
  464. rucio-32.8.6.data/scripts/rucio-judge-repairer +45 -0
  465. rucio-32.8.6.data/scripts/rucio-kronos +45 -0
  466. rucio-32.8.6.data/scripts/rucio-light-reaper +53 -0
  467. rucio-32.8.6.data/scripts/rucio-minos +54 -0
  468. rucio-32.8.6.data/scripts/rucio-minos-temporary-expiration +51 -0
  469. rucio-32.8.6.data/scripts/rucio-necromancer +121 -0
  470. rucio-32.8.6.data/scripts/rucio-oauth-manager +64 -0
  471. rucio-32.8.6.data/scripts/rucio-reaper +84 -0
  472. rucio-32.8.6.data/scripts/rucio-replica-recoverer +249 -0
  473. rucio-32.8.6.data/scripts/rucio-storage-consistency-actions +75 -0
  474. rucio-32.8.6.data/scripts/rucio-transmogrifier +78 -0
  475. rucio-32.8.6.data/scripts/rucio-undertaker +77 -0
  476. rucio-32.8.6.dist-info/METADATA +83 -0
  477. rucio-32.8.6.dist-info/RECORD +481 -0
  478. rucio-32.8.6.dist-info/WHEEL +5 -0
  479. rucio-32.8.6.dist-info/licenses/AUTHORS.rst +94 -0
  480. rucio-32.8.6.dist-info/licenses/LICENSE +201 -0
  481. rucio-32.8.6.dist-info/top_level.txt +1 -0
@@ -0,0 +1,753 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright European Organization for Nuclear Research (CERN) since 2012
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ import functools
17
+ import logging
18
+ import re
19
+ import threading
20
+ import time
21
+ from collections.abc import Callable
22
+ from datetime import datetime
23
+ from json import loads, dumps
24
+ from typing import TYPE_CHECKING
25
+
26
+ import rucio.db.sqla.util
27
+ from rucio.common.config import config_get
28
+ from rucio.common.exception import (
29
+ DatabaseException,
30
+ InvalidReplicationRule,
31
+ DuplicateRule,
32
+ InvalidRSEExpression,
33
+ InsufficientTargetRSEs,
34
+ InsufficientAccountLimit,
35
+ RSEOverQuota,
36
+ InvalidRuleWeight,
37
+ StagingAreaRuleRequiresLifetime,
38
+ SubscriptionWrongParameter,
39
+ SubscriptionNotFound,
40
+ )
41
+ from rucio.common.logging import setup_logging
42
+ from rucio.common.stopwatch import Stopwatch
43
+ from rucio.common.types import InternalAccount
44
+ from rucio.common.utils import chunks
45
+ from rucio.core.did import list_new_dids, set_new_dids, get_metadata
46
+ from rucio.core.monitor import MetricManager
47
+ from rucio.core.rse import list_rses, rse_exists, get_rse_id, list_rse_attributes
48
+ from rucio.core.rse_expression_parser import parse_expression
49
+ from rucio.core.rse_selector import resolve_rse_expression
50
+ from rucio.core.rule import add_rule, list_rules, get_rule
51
+ from rucio.core.subscription import list_subscriptions, update_subscription
52
+ from rucio.daemons.common import run_daemon
53
+ from rucio.db.sqla.constants import DIDType, SubscriptionState
54
+
55
+ if TYPE_CHECKING:
56
+ from types import FrameType
57
+ from typing import Optional
58
+
59
+ from rucio.daemons.common import HeartbeatHandler
60
+ from rucio.common.types import InternalScope
61
+
62
+ METRICS = MetricManager(module=__name__)
63
+ graceful_stop = threading.Event()
64
+ DAEMON_NAME = "transmogrifier"
65
+
66
+ RULES_COMMENT_LENGTH = 255
67
+
68
+
69
+ def __get_rule_dict(rule_dict: dict, subscription: dict) -> dict:
70
+ """
71
+ Internal method to clean and enrich the rule_dict coming from the subscription.
72
+
73
+ :param rule_dict: The rule dictionnary coming from a subscription.
74
+ :param subscription: The subscription associated to the rule.
75
+ :return: A dictionary that contains all the parameters associated to the rule.
76
+ """
77
+ source_replica_expression = rule_dict.get("source_replica_expression", None)
78
+ rule_dict["source_replica_expression"] = source_replica_expression
79
+ locked = rule_dict.get("locked", None)
80
+ if locked == "True":
81
+ locked = True
82
+ else:
83
+ locked = False
84
+ rule_dict["locked"] = locked
85
+
86
+ purge_replicas = rule_dict.get("purge_replicas", False)
87
+ if purge_replicas == "True":
88
+ purge_replicas = True
89
+ else:
90
+ purge_replicas = False
91
+ rule_dict["purge_replicas"] = purge_replicas
92
+
93
+ rule_dict["rse_expression"] = str(rule_dict["rse_expression"])
94
+ comment = str(subscription["comments"])[:RULES_COMMENT_LENGTH]
95
+ if "comments" in rule_dict:
96
+ comment = str(rule_dict["comments"])
97
+ rule_dict["comment"] = comment
98
+ account = subscription["account"]
99
+ if "account" in rule_dict:
100
+ vo = account.vo
101
+ account = InternalAccount(rule_dict["account"], vo=vo)
102
+ rule_dict["account"] = account
103
+ rule_dict["copies"] = int(rule_dict["copies"])
104
+ default_activity = config_get("rules", "default_activity", default="default")
105
+ activity = rule_dict.get("activity", default_activity)
106
+ rule_dict["activity"] = activity
107
+ lifetime = rule_dict.get("lifetime", None)
108
+ if lifetime:
109
+ rule_dict["lifetime"] = int(lifetime)
110
+ chained_idx = rule_dict.get("chained_idx", None)
111
+ if chained_idx:
112
+ chained_idx = int(rule_dict["copies"])
113
+ rule_dict["chained_idx"] = chained_idx
114
+ delay_injection = rule_dict.get("delay_injection", None)
115
+ if delay_injection:
116
+ delay_injection = int(delay_injection)
117
+ rule_dict["delay_injection"] = delay_injection
118
+ return rule_dict
119
+
120
+
121
+ def __split_rule_select_rses(
122
+ subscription_id: str,
123
+ subscription_name: str,
124
+ scope: "InternalScope",
125
+ name: str,
126
+ account: "InternalAccount",
127
+ weight: int,
128
+ rse_expression: str,
129
+ copies: int,
130
+ blocklisted_rse_id: list,
131
+ logger: "Callable",
132
+ ) -> tuple[list, bool, bool]:
133
+ """
134
+ Internal method to create a list of RSEs that match RSE expression for subscriptions with split_rule.
135
+
136
+ :param subscription_id: The subscription id.
137
+ :param subscription_name: The subscription name.
138
+ :param scope: The internal DID scope.
139
+ :param name: The DID name.
140
+ :param account: The internal account.
141
+ :param weight: The weight of the rule.
142
+ :param rse_expression: The RSE expression of the rule.
143
+ :param copies: The number of copies.
144
+ :param blocklisted_rse_id: The list of blocklisted_rse_id.
145
+ :param logger: The logger.
146
+ :return: A tuple with list selected_rses, and 2 booleans create_rule, wont_reevaluate.
147
+ """
148
+ wont_reevaluate = False
149
+ create_rule = True
150
+ preferred_rses = set()
151
+ for rule in list_rules(
152
+ filters={
153
+ "subscription_id": subscription_id,
154
+ "scope": scope,
155
+ "name": name,
156
+ }
157
+ ):
158
+ for rse_dict in parse_expression(
159
+ rule["rse_expression"],
160
+ filter_={"vo": account.vo},
161
+ ):
162
+ preferred_rses.add(rse_dict["rse"])
163
+ preferred_rses = list(preferred_rses)
164
+ preferred_unmatched = list()
165
+ selected_rses = list()
166
+
167
+ for attempt in range(0, 2):
168
+ # First attempt excludes blocklisted RSEs
169
+ # Second attempt includes blocklisted RSEs
170
+ try:
171
+ (selected_rses, preferred_unmatched,) = resolve_rse_expression(
172
+ rse_expression,
173
+ account,
174
+ weight=weight,
175
+ copies=copies,
176
+ size=0,
177
+ preferred_rses=preferred_rses,
178
+ blocklist=blocklisted_rse_id,
179
+ )
180
+ wont_reevaluate = True
181
+ break
182
+ except (
183
+ InsufficientTargetRSEs,
184
+ InsufficientAccountLimit,
185
+ InvalidRuleWeight,
186
+ RSEOverQuota,
187
+ ) as error:
188
+ logger(
189
+ logging.WARNING,
190
+ 'Problem getting RSEs for subscription "%s" for account %s : %s. %s'
191
+ % (
192
+ subscription_name,
193
+ account,
194
+ str(error),
195
+ 'Try including blocklisted sites' if attempt == 0 else 'Skipping rule creation.'
196
+ ),
197
+ )
198
+ # Now including the blocklisted sites
199
+ blocklisted_rse_id = []
200
+ METRICS.counter(name="addnewrule.errortype.{exception}").labels(exception=str(error.__class__.__name__)).inc()
201
+ wont_reevaluate = True
202
+ except Exception as error:
203
+ logger(
204
+ logging.ERROR,
205
+ "Problem resolving RSE expression %s : %s"
206
+ % (
207
+ rse_expression,
208
+ str(error),
209
+ )
210
+ )
211
+ if len(preferred_rses) - len(preferred_unmatched) >= copies:
212
+ create_rule = False
213
+ return selected_rses, create_rule, wont_reevaluate
214
+
215
+
216
+ def get_subscriptions(logger: Callable = logging.log) -> list[dict]:
217
+ """
218
+ A method to extract the list of active subscriptions and exclued the one that have bad RSE expression.
219
+ :param logger: The logger.
220
+ :return: The list of active subscriptions.
221
+ """
222
+ subscriptions = []
223
+ try:
224
+ sub_dict = {3: []}
225
+ # Get the list of subscriptions. The default priority of the subscription is 3. 0 is the highest priority, 5 the lowest
226
+ # The priority is defined as 'policyid'
227
+ logger(logging.DEBUG, "Listing active subscriptions")
228
+ for sub in list_subscriptions(None, None):
229
+ rse_expression = sub.get("rse_expression")
230
+ skip_sub = False
231
+ rules = loads(sub["replication_rules"])
232
+ overwrite_rules = False
233
+ for rule in rules:
234
+ rse_expression = rule.get("rse_expression")
235
+ try:
236
+ list_rses_from_expression = parse_expression(rse_expression)
237
+ except InvalidRSEExpression:
238
+ logger(
239
+ logging.ERROR,
240
+ "Invalid RSE expression %s for subscription %s. Subscription removed from the list",
241
+ rse_expression,
242
+ sub["id"],
243
+ )
244
+ skip_sub = True
245
+ break
246
+ if rule.get("copies") == "*":
247
+ rule["copies"] = len(list_rses_from_expression)
248
+ overwrite_rules = True
249
+ if skip_sub:
250
+ continue
251
+ if overwrite_rules:
252
+ sub["replication_rules"] = dumps(rules)
253
+ if (
254
+ sub["state"] != SubscriptionState.INACTIVE
255
+ and sub["lifetime"]
256
+ and (datetime.now() > sub["lifetime"])
257
+ ):
258
+ update_subscription(
259
+ name=sub["name"],
260
+ account=sub["account"],
261
+ metadata={"state": SubscriptionState.INACTIVE},
262
+ )
263
+
264
+ elif sub["state"] in [SubscriptionState.ACTIVE, SubscriptionState.UPDATED]:
265
+ priority = 3
266
+ if "policyid" in sub:
267
+ if int(sub["policyid"]) not in sub_dict:
268
+ sub_dict[int(sub["policyid"])] = []
269
+ priority = int(sub["policyid"])
270
+ sub_dict[priority].append(sub)
271
+ priorities = list(sub_dict.keys())
272
+ priorities.sort()
273
+ # Order the subscriptions according to their priority
274
+ for priority in priorities:
275
+ subscriptions.extend(sub_dict[priority])
276
+ logger(logging.INFO, "%i active subscriptions", len(subscriptions))
277
+ except SubscriptionNotFound as error:
278
+ logger(logging.WARNING, "No subscriptions defined: %s" % (str(error)))
279
+ return []
280
+ except TypeError as error:
281
+ logger(
282
+ logging.ERROR,
283
+ "Failed to parse subscription: %s" % (str(error)),
284
+ )
285
+ raise error
286
+ except Exception as error:
287
+ logger(
288
+ logging.ERROR,
289
+ "Failed to get list of new DIDs or subscriptions: %s" % (str(error)),
290
+ )
291
+ raise error
292
+ return subscriptions
293
+
294
+
295
+ def __is_matching_subscription(subscription, did, metadata):
296
+ """
297
+ Internal method to identify if a DID matches a subscription.
298
+
299
+ :param subscription: The subscription dictionary.
300
+ :param did: The DID dictionary
301
+ :param metadata: The metadata dictionnary for the DID
302
+ :return: True/False
303
+ """
304
+ if metadata["hidden"]:
305
+ return False
306
+ try:
307
+ filter_string = loads(subscription["filter"])
308
+ except ValueError as error:
309
+ logging.error("%s : Subscription will be skipped" % error)
310
+ return False
311
+ # Loop over the keys of filter_string for subscription
312
+ for key in filter_string:
313
+ values = filter_string[key]
314
+ if key == "pattern":
315
+ if not re.match(values, did["name"]):
316
+ return False
317
+ elif key == "excluded_pattern":
318
+ if re.match(values, did["name"]):
319
+ return False
320
+ elif key == "split_rule":
321
+ pass
322
+ elif key == "scope":
323
+ match_scope = False
324
+ for scope in values:
325
+ if re.match(scope, did["scope"].internal):
326
+ match_scope = True
327
+ break
328
+ if not match_scope:
329
+ return False
330
+ elif key == "account":
331
+ match_account = False
332
+ if not isinstance(values, list):
333
+ values = [values]
334
+ for account in values:
335
+ if account == metadata["account"].internal:
336
+ match_account = True
337
+ break
338
+ if not match_account:
339
+ return False
340
+ elif key == "did_type":
341
+ match_did_type = False
342
+ if not isinstance(values, list):
343
+ values = [values]
344
+ for did_type in values:
345
+ if did_type == metadata["did_type"].name:
346
+ match_did_type = True
347
+ break
348
+ if not match_did_type:
349
+ return False
350
+ elif key in ["min_avg_file_size", "max_avg_file_size"]:
351
+ length = metadata["length"]
352
+ size = metadata["bytes"]
353
+ if length and size:
354
+ avg_file_size = size / length
355
+ if key == "min_avg_file_size" and avg_file_size < values:
356
+ return False
357
+ if key == "max_avg_file_size" and avg_file_size > values:
358
+ return False
359
+ else:
360
+ # If the DID is evaluated at the creation, length and bytes are not set yet
361
+ # In that case, just ignore min_avg_file_size and max_avg_file_size filter
362
+ continue
363
+ else:
364
+ if not isinstance(values, list):
365
+ values = [
366
+ values,
367
+ ]
368
+ has_metadata = False
369
+ for meta in metadata:
370
+ if str(meta) == str(key):
371
+ has_metadata = True
372
+ match_meta = False
373
+ for value in values:
374
+ if re.match(str(value), str(metadata[meta])):
375
+ match_meta = True
376
+ break
377
+ if not match_meta:
378
+ return False
379
+ if not has_metadata:
380
+ return False
381
+ return True
382
+
383
+
384
+ def select_algorithm(algorithm: str, rule_ids: list, params: dict, logger: "Callable") -> dict:
385
+ """
386
+ Method used in case of chained subscriptions
387
+
388
+ :param algorithm: Algorithm used for the chained rule. Now only associated_site
389
+ associated_site : Choose an associated endpoint according to the RSE attribute assoiciated_site
390
+ :param rule_ids: List of parent rules
391
+ :param params: Dictionary of rules parameters to be used by the algorithm
392
+ """
393
+ selected_rses = {}
394
+ for rule_id in rule_ids:
395
+ rule = get_rule(rule_id)
396
+ logging.debug("In select_algorithm, %s", str(rule))
397
+ rse = rule["rse_expression"]
398
+ vo = rule["account"].vo
399
+ if rse_exists(rse, vo=vo):
400
+ rse_id = get_rse_id(rse, vo=vo)
401
+ rse_attributes = list_rse_attributes(rse_id)
402
+ if algorithm == "associated_site":
403
+ associated_sites = rse_attributes.get("associated_sites", None)
404
+ associated_site_idx = params.get("associated_site_idx", None)
405
+ if not associated_site_idx:
406
+ raise SubscriptionWrongParameter(
407
+ "Missing parameter associated_site_idx"
408
+ )
409
+ if associated_sites:
410
+ associated_sites = associated_sites.split(",")
411
+ if associated_site_idx > len(associated_sites) + 1:
412
+ raise SubscriptionWrongParameter(
413
+ "Parameter associated_site_idx is out of range"
414
+ )
415
+ associated_site = associated_sites[associated_site_idx - 1]
416
+ selected_rses[associated_site] = {
417
+ "source_replica_expression": rse,
418
+ "weight": None,
419
+ }
420
+ if algorithm == "exclude_site":
421
+ site = rse_attributes.get("site", None)
422
+ rse_expression = params['rse_expression'] + '\\site=%s' % site
423
+ (
424
+ selected_rses,
425
+ create_rule,
426
+ wont_reevaluate,
427
+ ) = __split_rule_select_rses(
428
+ subscription_id=params["subscription_id"],
429
+ subscription_name=params["subscription_name"],
430
+ scope=rule["scope"],
431
+ name=rule["name"],
432
+ account=rule.get("account"),
433
+ weight=rule.get("weight"),
434
+ rse_expression=rse_expression,
435
+ copies=rule.get('copies'),
436
+ blocklisted_rse_id=params['blocklisted_rse_id'],
437
+ logger=logger,
438
+ )
439
+ dict_selected_rses = {}
440
+ for entry in selected_rses:
441
+ dict_selected_rses[entry] = {
442
+ "source_replica_expression": rse,
443
+ "weight": None,
444
+ }
445
+ selected_rses = dict_selected_rses
446
+ else:
447
+ raise SubscriptionWrongParameter(
448
+ "Algorithm %s only works with split_rule" % algorithm
449
+ )
450
+ if rule["copies"] != 1:
451
+ raise SubscriptionWrongParameter(
452
+ "Algorithm %s only works with split_rule" % algorithm
453
+ )
454
+ return selected_rses
455
+
456
+
457
+ def transmogrifier(bulk: int = 5, once: bool = False, sleep_time: int = 60) -> None:
458
+ """
459
+ Creates a Transmogrifier Worker that gets a list of new DIDs for a given hash,
460
+ identifies the subscriptions matching the DIDs and
461
+ submit a replication rule for each DID matching a subscription.
462
+
463
+ :param bulk: The number of requests to process.
464
+ :param once: Run only once.
465
+ :param sleep_time: Time between two cycles.
466
+ """
467
+ run_daemon(
468
+ once=once,
469
+ graceful_stop=graceful_stop,
470
+ executable=DAEMON_NAME,
471
+ partition_wait_time=1,
472
+ sleep_time=sleep_time,
473
+ run_once_fnc=functools.partial(
474
+ run_once,
475
+ bulk=bulk,
476
+ ),
477
+ )
478
+
479
+
480
+ def run_once(heartbeat_handler: "HeartbeatHandler", bulk: int, **_kwargs) -> bool:
481
+
482
+ worker_number, total_workers, logger = heartbeat_handler.live()
483
+ stopwatch = Stopwatch()
484
+ blocklisted_rse_id = [rse["id"] for rse in list_rses({"availability_write": False})]
485
+ identifiers = []
486
+ # List all the active subscriptions
487
+ subscriptions = get_subscriptions(logger=logger)
488
+
489
+ # Loop over all the new dids
490
+ # Get the new DIDs based on the is_new flag
491
+ logger(logging.DEBUG, "Listing new dids")
492
+ for did in list_new_dids(
493
+ thread=worker_number,
494
+ total_threads=total_workers,
495
+ chunk_size=bulk,
496
+ did_type=None,
497
+ ):
498
+ _, _, logger = heartbeat_handler.live()
499
+ did_success = True
500
+ if not (
501
+ did["did_type"] == DIDType.DATASET or did["did_type"] == DIDType.CONTAINER
502
+ ):
503
+ identifiers.append(
504
+ {
505
+ "scope": did["scope"],
506
+ "name": did["name"],
507
+ "did_type": did["did_type"],
508
+ }
509
+ )
510
+ continue
511
+ metadata = get_metadata(did["scope"], did["name"])
512
+
513
+ # Loop over all the subscriptions
514
+ for subscription in subscriptions:
515
+ # Check if the DID match the subscription
516
+ if __is_matching_subscription(subscription, did, metadata) is True:
517
+ filter_string = loads(subscription["filter"])
518
+ split_rule = filter_string.get("split_rule", False)
519
+ stime = time.time()
520
+ logger(
521
+ logging.INFO,
522
+ "%s:%s matches subscription %s"
523
+ % (did["scope"], did["name"], subscription["name"]),
524
+ )
525
+ rules = loads(subscription["replication_rules"])
526
+ created_rules = {}
527
+ for cnt, rule_dict in enumerate(rules):
528
+ created_rules[cnt + 1] = []
529
+ # Get all the rule and subscription parameters
530
+ rule_dict = __get_rule_dict(rule_dict, subscription)
531
+ weight = rule_dict.get("weight", None)
532
+ source_replica_expression = rule_dict.get(
533
+ "source_replica_expression", None
534
+ )
535
+ copies = rule_dict["copies"]
536
+ success = False
537
+
538
+ chained_idx = rule_dict.get("chained_idx", None)
539
+ # By default selected_rses contains only the rse_expression
540
+ # It is overwritten in 2 cases : Chained subscription and split_rule
541
+ selected_rses = [rule_dict.get("rse_expression")]
542
+ if chained_idx:
543
+ # In the case of chained subscription, don't use rseselector but use the rses returned by the algorithm
544
+ params = {}
545
+ params['rse_expression'] = rule_dict.get("rse_expression")
546
+ params['subscription_id'] = subscription["id"]
547
+ params['subscription_name'] = subscription["name"]
548
+ params['blocklisted_rse_id'] = blocklisted_rse_id
549
+ if rule_dict.get("associated_site_idx", None):
550
+ params["associated_site_idx"] = rule_dict.get(
551
+ "associated_site_idx", None
552
+ )
553
+ logger(
554
+ logging.DEBUG,
555
+ "Chained subscription identified. Will use %s",
556
+ str(created_rules[chained_idx]),
557
+ )
558
+ algorithm = rule_dict.get("algorithm", None)
559
+ selected_rses = select_algorithm(
560
+ algorithm,
561
+ created_rules[chained_idx],
562
+ params,
563
+ logger
564
+ )
565
+ copies = 1
566
+ elif split_rule:
567
+ (
568
+ selected_rses,
569
+ create_rule,
570
+ wont_reevaluate,
571
+ ) = __split_rule_select_rses(
572
+ subscription_id=subscription["id"],
573
+ subscription_name=subscription["name"],
574
+ scope=did["scope"],
575
+ name=did["name"],
576
+ account=rule_dict.get("account"),
577
+ weight=weight,
578
+ rse_expression=rule_dict.get("rse_expression"),
579
+ copies=copies,
580
+ blocklisted_rse_id=blocklisted_rse_id,
581
+ logger=logger,
582
+ )
583
+ copies = 1
584
+ if not create_rule:
585
+ continue
586
+ # The DID won't be reevaluated at the next cycle
587
+ did_success = did_success and wont_reevaluate
588
+
589
+ nb_rule = 0
590
+ # Try to create the rule
591
+ logger(logging.DEBUG, 'selected_rses : %s' % selected_rses)
592
+ try:
593
+ for rse in selected_rses:
594
+ if isinstance(selected_rses, dict):
595
+ # selected_rses is a dictionary only when split_rule is True or for chained subscriptions
596
+ source_replica_expression = selected_rses[rse].get(
597
+ "source_replica_expression",
598
+ None,
599
+ )
600
+ weight = selected_rses[rse].get("weight", None)
601
+ logger(
602
+ logging.INFO,
603
+ "Will insert one rule for %s:%s on %s"
604
+ % (did["scope"], did["name"], rse),
605
+ )
606
+ rule_ids = add_rule(
607
+ dids=[
608
+ {
609
+ "scope": did["scope"],
610
+ "name": did["name"],
611
+ }
612
+ ],
613
+ account=rule_dict.get("account"),
614
+ copies=copies,
615
+ rse_expression=rse,
616
+ grouping=rule_dict.get("grouping", "DATASET"),
617
+ weight=weight,
618
+ lifetime=rule_dict.get("lifetime", None),
619
+ locked=rule_dict.get("locked", None),
620
+ subscription_id=subscription["id"],
621
+ source_replica_expression=source_replica_expression,
622
+ activity=rule_dict.get("activity"),
623
+ purge_replicas=rule_dict.get("purge_replicas", False),
624
+ ignore_availability=rule_dict.get(
625
+ "ignore_availability", None
626
+ ),
627
+ comment=rule_dict.get("comment"),
628
+ delay_injection=rule_dict.get("delay_injection"),
629
+ )
630
+ created_rules[cnt + 1].append(rule_ids[0])
631
+ nb_rule += 1
632
+ if nb_rule == copies:
633
+ success = True
634
+ if split_rule:
635
+ success = True
636
+
637
+ METRICS.counter("addnewrule.done").inc(nb_rule)
638
+ METRICS.counter("addnewrule.activity.{activity}").labels(activity="".join(rule_dict.get("activity").split())).inc(nb_rule)
639
+ success = True
640
+ except (
641
+ InvalidReplicationRule,
642
+ InvalidRuleWeight,
643
+ InvalidRSEExpression,
644
+ StagingAreaRuleRequiresLifetime,
645
+ DuplicateRule,
646
+ ) as error:
647
+ # Errors that won't be retried
648
+ success = True
649
+ logger(logging.ERROR, str(error))
650
+ METRICS.counter("addnewrule.errortype.{exception}").labels(exception=str(error.__class__.__name__)).inc()
651
+ except Exception:
652
+ # Errors that will be retried
653
+ METRICS.counter("addnewrule.errortype.{exception}").labels(exception="unknown").inc()
654
+ logger(logging.ERROR, "Unexpected error", exc_info=True)
655
+
656
+ did_success = did_success and success
657
+ if not success:
658
+ logger(
659
+ logging.ERROR,
660
+ "Rule for %s:%s on %s cannot be inserted"
661
+ % (
662
+ did["scope"],
663
+ did["name"],
664
+ rule_dict.get("rse_expression"),
665
+ ),
666
+ )
667
+ else:
668
+ logger(
669
+ logging.INFO,
670
+ "%s rule(s) inserted in %f seconds"
671
+ % (str(nb_rule), time.time() - stime),
672
+ )
673
+
674
+ if did_success:
675
+ if did["did_type"] == str(DIDType.FILE):
676
+ METRICS.counter(name="files_processed").inc()
677
+ elif did["did_type"] == str(DIDType.DATASET):
678
+ METRICS.counter(name="datasets_processed").inc()
679
+ elif did["did_type"] == str(DIDType.CONTAINER):
680
+ METRICS.counter(name="containers_processed").inc()
681
+ METRICS.counter(name="dids_processed").inc()
682
+ identifiers.append(
683
+ {
684
+ "scope": did["scope"],
685
+ "name": did["name"],
686
+ "did_type": did["did_type"],
687
+ }
688
+ )
689
+
690
+ # Mark the DIDs as processed
691
+ flag_stopwatch = Stopwatch()
692
+ for identifier in chunks(identifiers, 100):
693
+ set_new_dids(identifier, None)
694
+ logger(logging.DEBUG, "Time to set the new flag : %f" % flag_stopwatch.elapsed)
695
+
696
+ stopwatch.stop()
697
+
698
+ for sub in subscriptions:
699
+ update_subscription(
700
+ name=sub["name"],
701
+ account=sub["account"],
702
+ metadata={"last_processed": datetime.utcnow()},
703
+ )
704
+ logger(
705
+ logging.INFO,
706
+ "It took %f seconds to process %i DIDs" % (stopwatch.elapsed, len(identifiers)),
707
+ )
708
+ logger(logging.DEBUG, "DIDs processed : %s" % (str(identifiers)))
709
+ METRICS.counter(name="transmogrifier.job.done").inc(1)
710
+ METRICS.timer("job.duration").observe(stopwatch.elapsed)
711
+ must_sleep = True
712
+ return must_sleep
713
+
714
+
715
+ def run(
716
+ threads: int = 1, bulk: int = 100, once: bool = False, sleep_time: int = 60
717
+ ) -> None:
718
+ """
719
+ Starts up the transmogrifier threads.
720
+ """
721
+ setup_logging(process_name=DAEMON_NAME)
722
+
723
+ if rucio.db.sqla.util.is_old_db():
724
+ raise DatabaseException("Database was not updated, daemon won't start")
725
+
726
+ if once:
727
+ logging.info("Will run only one iteration in a single threaded mode")
728
+ transmogrifier(bulk=bulk, once=once)
729
+ else:
730
+ logging.info("starting transmogrifier threads")
731
+ thread_list = [
732
+ threading.Thread(
733
+ target=transmogrifier,
734
+ kwargs={"once": once, "sleep_time": sleep_time, "bulk": bulk},
735
+ )
736
+ for _ in range(0, threads)
737
+ ]
738
+ [thread.start() for thread in thread_list]
739
+ logging.info("waiting for interrupts")
740
+ # Interruptible joins require a timeout.
741
+ while thread_list:
742
+ thread_list = [
743
+ thread.join(timeout=3.14)
744
+ for thread in thread_list
745
+ if thread and thread.is_alive()
746
+ ]
747
+
748
+
749
+ def stop(signum: "Optional[int]" = None, frame: "Optional[FrameType]" = None) -> None:
750
+ """
751
+ Graceful exit.
752
+ """
753
+ graceful_stop.set()