rucio 37.0.0rc1__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 (487) hide show
  1. rucio/__init__.py +17 -0
  2. rucio/alembicrevision.py +15 -0
  3. rucio/cli/__init__.py +14 -0
  4. rucio/cli/account.py +216 -0
  5. rucio/cli/bin_legacy/__init__.py +13 -0
  6. rucio/cli/bin_legacy/rucio.py +2825 -0
  7. rucio/cli/bin_legacy/rucio_admin.py +2500 -0
  8. rucio/cli/command.py +272 -0
  9. rucio/cli/config.py +72 -0
  10. rucio/cli/did.py +191 -0
  11. rucio/cli/download.py +128 -0
  12. rucio/cli/lifetime_exception.py +33 -0
  13. rucio/cli/replica.py +162 -0
  14. rucio/cli/rse.py +293 -0
  15. rucio/cli/rule.py +158 -0
  16. rucio/cli/scope.py +40 -0
  17. rucio/cli/subscription.py +73 -0
  18. rucio/cli/upload.py +60 -0
  19. rucio/cli/utils.py +226 -0
  20. rucio/client/__init__.py +15 -0
  21. rucio/client/accountclient.py +432 -0
  22. rucio/client/accountlimitclient.py +183 -0
  23. rucio/client/baseclient.py +983 -0
  24. rucio/client/client.py +120 -0
  25. rucio/client/configclient.py +126 -0
  26. rucio/client/credentialclient.py +59 -0
  27. rucio/client/didclient.py +868 -0
  28. rucio/client/diracclient.py +56 -0
  29. rucio/client/downloadclient.py +1783 -0
  30. rucio/client/exportclient.py +44 -0
  31. rucio/client/fileclient.py +50 -0
  32. rucio/client/importclient.py +42 -0
  33. rucio/client/lifetimeclient.py +90 -0
  34. rucio/client/lockclient.py +109 -0
  35. rucio/client/metaconventionsclient.py +140 -0
  36. rucio/client/pingclient.py +44 -0
  37. rucio/client/replicaclient.py +452 -0
  38. rucio/client/requestclient.py +125 -0
  39. rucio/client/richclient.py +317 -0
  40. rucio/client/rseclient.py +746 -0
  41. rucio/client/ruleclient.py +294 -0
  42. rucio/client/scopeclient.py +90 -0
  43. rucio/client/subscriptionclient.py +173 -0
  44. rucio/client/touchclient.py +82 -0
  45. rucio/client/uploadclient.py +969 -0
  46. rucio/common/__init__.py +13 -0
  47. rucio/common/bittorrent.py +234 -0
  48. rucio/common/cache.py +111 -0
  49. rucio/common/checksum.py +168 -0
  50. rucio/common/client.py +122 -0
  51. rucio/common/config.py +788 -0
  52. rucio/common/constants.py +217 -0
  53. rucio/common/constraints.py +17 -0
  54. rucio/common/didtype.py +237 -0
  55. rucio/common/dumper/__init__.py +342 -0
  56. rucio/common/dumper/consistency.py +497 -0
  57. rucio/common/dumper/data_models.py +362 -0
  58. rucio/common/dumper/path_parsing.py +75 -0
  59. rucio/common/exception.py +1208 -0
  60. rucio/common/extra.py +31 -0
  61. rucio/common/logging.py +420 -0
  62. rucio/common/pcache.py +1409 -0
  63. rucio/common/plugins.py +185 -0
  64. rucio/common/policy.py +93 -0
  65. rucio/common/schema/__init__.py +200 -0
  66. rucio/common/schema/generic.py +416 -0
  67. rucio/common/schema/generic_multi_vo.py +395 -0
  68. rucio/common/stomp_utils.py +423 -0
  69. rucio/common/stopwatch.py +55 -0
  70. rucio/common/test_rucio_server.py +154 -0
  71. rucio/common/types.py +483 -0
  72. rucio/common/utils.py +1688 -0
  73. rucio/core/__init__.py +13 -0
  74. rucio/core/account.py +496 -0
  75. rucio/core/account_counter.py +236 -0
  76. rucio/core/account_limit.py +425 -0
  77. rucio/core/authentication.py +620 -0
  78. rucio/core/config.py +437 -0
  79. rucio/core/credential.py +224 -0
  80. rucio/core/did.py +3004 -0
  81. rucio/core/did_meta_plugins/__init__.py +252 -0
  82. rucio/core/did_meta_plugins/did_column_meta.py +331 -0
  83. rucio/core/did_meta_plugins/did_meta_plugin_interface.py +165 -0
  84. rucio/core/did_meta_plugins/elasticsearch_meta.py +407 -0
  85. rucio/core/did_meta_plugins/filter_engine.py +672 -0
  86. rucio/core/did_meta_plugins/json_meta.py +240 -0
  87. rucio/core/did_meta_plugins/mongo_meta.py +229 -0
  88. rucio/core/did_meta_plugins/postgres_meta.py +352 -0
  89. rucio/core/dirac.py +237 -0
  90. rucio/core/distance.py +187 -0
  91. rucio/core/exporter.py +59 -0
  92. rucio/core/heartbeat.py +363 -0
  93. rucio/core/identity.py +301 -0
  94. rucio/core/importer.py +260 -0
  95. rucio/core/lifetime_exception.py +377 -0
  96. rucio/core/lock.py +577 -0
  97. rucio/core/message.py +288 -0
  98. rucio/core/meta_conventions.py +203 -0
  99. rucio/core/monitor.py +448 -0
  100. rucio/core/naming_convention.py +195 -0
  101. rucio/core/nongrid_trace.py +136 -0
  102. rucio/core/oidc.py +1463 -0
  103. rucio/core/permission/__init__.py +161 -0
  104. rucio/core/permission/generic.py +1124 -0
  105. rucio/core/permission/generic_multi_vo.py +1144 -0
  106. rucio/core/quarantined_replica.py +224 -0
  107. rucio/core/replica.py +4483 -0
  108. rucio/core/replica_sorter.py +362 -0
  109. rucio/core/request.py +3091 -0
  110. rucio/core/rse.py +2079 -0
  111. rucio/core/rse_counter.py +185 -0
  112. rucio/core/rse_expression_parser.py +459 -0
  113. rucio/core/rse_selector.py +304 -0
  114. rucio/core/rule.py +4484 -0
  115. rucio/core/rule_grouping.py +1620 -0
  116. rucio/core/scope.py +181 -0
  117. rucio/core/subscription.py +362 -0
  118. rucio/core/topology.py +490 -0
  119. rucio/core/trace.py +375 -0
  120. rucio/core/transfer.py +1531 -0
  121. rucio/core/vo.py +169 -0
  122. rucio/core/volatile_replica.py +151 -0
  123. rucio/daemons/__init__.py +13 -0
  124. rucio/daemons/abacus/__init__.py +13 -0
  125. rucio/daemons/abacus/account.py +116 -0
  126. rucio/daemons/abacus/collection_replica.py +124 -0
  127. rucio/daemons/abacus/rse.py +117 -0
  128. rucio/daemons/atropos/__init__.py +13 -0
  129. rucio/daemons/atropos/atropos.py +242 -0
  130. rucio/daemons/auditor/__init__.py +289 -0
  131. rucio/daemons/auditor/hdfs.py +97 -0
  132. rucio/daemons/auditor/srmdumps.py +355 -0
  133. rucio/daemons/automatix/__init__.py +13 -0
  134. rucio/daemons/automatix/automatix.py +304 -0
  135. rucio/daemons/badreplicas/__init__.py +13 -0
  136. rucio/daemons/badreplicas/minos.py +322 -0
  137. rucio/daemons/badreplicas/minos_temporary_expiration.py +171 -0
  138. rucio/daemons/badreplicas/necromancer.py +196 -0
  139. rucio/daemons/bb8/__init__.py +13 -0
  140. rucio/daemons/bb8/bb8.py +353 -0
  141. rucio/daemons/bb8/common.py +759 -0
  142. rucio/daemons/bb8/nuclei_background_rebalance.py +153 -0
  143. rucio/daemons/bb8/t2_background_rebalance.py +153 -0
  144. rucio/daemons/cache/__init__.py +13 -0
  145. rucio/daemons/cache/consumer.py +133 -0
  146. rucio/daemons/common.py +405 -0
  147. rucio/daemons/conveyor/__init__.py +13 -0
  148. rucio/daemons/conveyor/common.py +562 -0
  149. rucio/daemons/conveyor/finisher.py +529 -0
  150. rucio/daemons/conveyor/poller.py +394 -0
  151. rucio/daemons/conveyor/preparer.py +205 -0
  152. rucio/daemons/conveyor/receiver.py +179 -0
  153. rucio/daemons/conveyor/stager.py +133 -0
  154. rucio/daemons/conveyor/submitter.py +403 -0
  155. rucio/daemons/conveyor/throttler.py +532 -0
  156. rucio/daemons/follower/__init__.py +13 -0
  157. rucio/daemons/follower/follower.py +101 -0
  158. rucio/daemons/hermes/__init__.py +13 -0
  159. rucio/daemons/hermes/hermes.py +534 -0
  160. rucio/daemons/judge/__init__.py +13 -0
  161. rucio/daemons/judge/cleaner.py +159 -0
  162. rucio/daemons/judge/evaluator.py +185 -0
  163. rucio/daemons/judge/injector.py +162 -0
  164. rucio/daemons/judge/repairer.py +154 -0
  165. rucio/daemons/oauthmanager/__init__.py +13 -0
  166. rucio/daemons/oauthmanager/oauthmanager.py +198 -0
  167. rucio/daemons/reaper/__init__.py +13 -0
  168. rucio/daemons/reaper/dark_reaper.py +282 -0
  169. rucio/daemons/reaper/reaper.py +739 -0
  170. rucio/daemons/replicarecoverer/__init__.py +13 -0
  171. rucio/daemons/replicarecoverer/suspicious_replica_recoverer.py +626 -0
  172. rucio/daemons/rsedecommissioner/__init__.py +13 -0
  173. rucio/daemons/rsedecommissioner/config.py +81 -0
  174. rucio/daemons/rsedecommissioner/profiles/__init__.py +24 -0
  175. rucio/daemons/rsedecommissioner/profiles/atlas.py +60 -0
  176. rucio/daemons/rsedecommissioner/profiles/generic.py +452 -0
  177. rucio/daemons/rsedecommissioner/profiles/types.py +93 -0
  178. rucio/daemons/rsedecommissioner/rse_decommissioner.py +280 -0
  179. rucio/daemons/storage/__init__.py +13 -0
  180. rucio/daemons/storage/consistency/__init__.py +13 -0
  181. rucio/daemons/storage/consistency/actions.py +848 -0
  182. rucio/daemons/tracer/__init__.py +13 -0
  183. rucio/daemons/tracer/kronos.py +511 -0
  184. rucio/daemons/transmogrifier/__init__.py +13 -0
  185. rucio/daemons/transmogrifier/transmogrifier.py +762 -0
  186. rucio/daemons/undertaker/__init__.py +13 -0
  187. rucio/daemons/undertaker/undertaker.py +137 -0
  188. rucio/db/__init__.py +13 -0
  189. rucio/db/sqla/__init__.py +52 -0
  190. rucio/db/sqla/constants.py +206 -0
  191. rucio/db/sqla/migrate_repo/__init__.py +13 -0
  192. rucio/db/sqla/migrate_repo/env.py +110 -0
  193. rucio/db/sqla/migrate_repo/versions/01eaf73ab656_add_new_rule_notification_state_progress.py +70 -0
  194. rucio/db/sqla/migrate_repo/versions/0437a40dbfd1_add_eol_at_in_rules.py +47 -0
  195. rucio/db/sqla/migrate_repo/versions/0f1adb7a599a_create_transfer_hops_table.py +59 -0
  196. rucio/db/sqla/migrate_repo/versions/102efcf145f4_added_stuck_at_column_to_rules.py +43 -0
  197. rucio/db/sqla/migrate_repo/versions/13d4f70c66a9_introduce_transfer_limits.py +91 -0
  198. rucio/db/sqla/migrate_repo/versions/140fef722e91_cleanup_distances_table.py +76 -0
  199. rucio/db/sqla/migrate_repo/versions/14ec5aeb64cf_add_request_external_host.py +43 -0
  200. rucio/db/sqla/migrate_repo/versions/156fb5b5a14_add_request_type_to_requests_idx.py +50 -0
  201. rucio/db/sqla/migrate_repo/versions/1677d4d803c8_split_rse_availability_into_multiple.py +68 -0
  202. rucio/db/sqla/migrate_repo/versions/16a0aca82e12_create_index_on_table_replicas_path.py +40 -0
  203. rucio/db/sqla/migrate_repo/versions/1803333ac20f_adding_provenance_and_phys_group.py +45 -0
  204. rucio/db/sqla/migrate_repo/versions/1a29d6a9504c_add_didtype_chck_to_requests.py +60 -0
  205. rucio/db/sqla/migrate_repo/versions/1a80adff031a_create_index_on_rules_hist_recent.py +40 -0
  206. rucio/db/sqla/migrate_repo/versions/1c45d9730ca6_increase_identity_length.py +140 -0
  207. rucio/db/sqla/migrate_repo/versions/1d1215494e95_add_quarantined_replicas_table.py +73 -0
  208. rucio/db/sqla/migrate_repo/versions/1d96f484df21_asynchronous_rules_and_rule_approval.py +74 -0
  209. rucio/db/sqla/migrate_repo/versions/1f46c5f240ac_add_bytes_column_to_bad_replicas.py +43 -0
  210. rucio/db/sqla/migrate_repo/versions/1fc15ab60d43_add_message_history_table.py +50 -0
  211. rucio/db/sqla/migrate_repo/versions/2190e703eb6e_move_rse_settings_to_rse_attributes.py +134 -0
  212. rucio/db/sqla/migrate_repo/versions/21d6b9dc9961_add_mismatch_scheme_state_to_requests.py +64 -0
  213. rucio/db/sqla/migrate_repo/versions/22cf51430c78_add_availability_column_to_table_rses.py +39 -0
  214. rucio/db/sqla/migrate_repo/versions/22d887e4ec0a_create_sources_table.py +64 -0
  215. rucio/db/sqla/migrate_repo/versions/25821a8a45a3_remove_unique_constraint_on_requests.py +51 -0
  216. rucio/db/sqla/migrate_repo/versions/25fc855625cf_added_unique_constraint_to_rules.py +41 -0
  217. rucio/db/sqla/migrate_repo/versions/269fee20dee9_add_repair_cnt_to_locks.py +43 -0
  218. rucio/db/sqla/migrate_repo/versions/271a46ea6244_add_ignore_availability_column_to_rules.py +44 -0
  219. rucio/db/sqla/migrate_repo/versions/277b5fbb41d3_switch_heartbeats_executable.py +53 -0
  220. rucio/db/sqla/migrate_repo/versions/27e3a68927fb_remove_replicas_tombstone_and_replicas_.py +38 -0
  221. rucio/db/sqla/migrate_repo/versions/2854cd9e168_added_rule_id_column.py +47 -0
  222. rucio/db/sqla/migrate_repo/versions/295289b5a800_processed_by_and__at_in_requests.py +45 -0
  223. rucio/db/sqla/migrate_repo/versions/2962ece31cf4_add_nbaccesses_column_in_the_did_table.py +45 -0
  224. rucio/db/sqla/migrate_repo/versions/2af3291ec4c_added_replicas_history_table.py +57 -0
  225. rucio/db/sqla/migrate_repo/versions/2b69addda658_add_columns_for_third_party_copy_read_.py +45 -0
  226. rucio/db/sqla/migrate_repo/versions/2b8e7bcb4783_add_config_table.py +69 -0
  227. rucio/db/sqla/migrate_repo/versions/2ba5229cb54c_add_submitted_at_to_requests_table.py +43 -0
  228. rucio/db/sqla/migrate_repo/versions/2cbee484dcf9_added_column_volume_to_rse_transfer_.py +42 -0
  229. rucio/db/sqla/migrate_repo/versions/2edee4a83846_add_source_to_requests_and_requests_.py +47 -0
  230. rucio/db/sqla/migrate_repo/versions/2eef46be23d4_change_tokens_pk.py +46 -0
  231. rucio/db/sqla/migrate_repo/versions/2f648fc909f3_index_in_rule_history_on_scope_name.py +40 -0
  232. rucio/db/sqla/migrate_repo/versions/3082b8cef557_add_naming_convention_table_and_closed_.py +67 -0
  233. rucio/db/sqla/migrate_repo/versions/30d5206e9cad_increase_oauthrequest_redirect_msg_.py +37 -0
  234. rucio/db/sqla/migrate_repo/versions/30fa38b6434e_add_index_on_service_column_in_the_message_table.py +44 -0
  235. rucio/db/sqla/migrate_repo/versions/3152492b110b_added_staging_area_column.py +77 -0
  236. rucio/db/sqla/migrate_repo/versions/32c7d2783f7e_create_bad_replicas_table.py +60 -0
  237. rucio/db/sqla/migrate_repo/versions/3345511706b8_replicas_table_pk_definition_is_in_.py +72 -0
  238. rucio/db/sqla/migrate_repo/versions/35ef10d1e11b_change_index_on_table_requests.py +42 -0
  239. rucio/db/sqla/migrate_repo/versions/379a19b5332d_create_rse_limits_table.py +65 -0
  240. rucio/db/sqla/migrate_repo/versions/384b96aa0f60_created_rule_history_tables.py +133 -0
  241. rucio/db/sqla/migrate_repo/versions/3ac1660a1a72_extend_distance_table.py +55 -0
  242. rucio/db/sqla/migrate_repo/versions/3ad36e2268b0_create_collection_replicas_updates_table.py +76 -0
  243. rucio/db/sqla/migrate_repo/versions/3c9df354071b_extend_waiting_request_state.py +60 -0
  244. rucio/db/sqla/migrate_repo/versions/3d9813fab443_add_a_new_state_lost_in_badfilesstatus.py +44 -0
  245. rucio/db/sqla/migrate_repo/versions/40ad39ce3160_add_transferred_at_to_requests_table.py +43 -0
  246. rucio/db/sqla/migrate_repo/versions/4207be2fd914_add_notification_column_to_rules.py +64 -0
  247. rucio/db/sqla/migrate_repo/versions/42db2617c364_create_index_on_requests_external_id.py +40 -0
  248. rucio/db/sqla/migrate_repo/versions/436827b13f82_added_column_activity_to_table_requests.py +43 -0
  249. rucio/db/sqla/migrate_repo/versions/44278720f774_update_requests_typ_sta_upd_idx_index.py +44 -0
  250. rucio/db/sqla/migrate_repo/versions/45378a1e76a8_create_collection_replica_table.py +78 -0
  251. rucio/db/sqla/migrate_repo/versions/469d262be19_removing_created_at_index.py +41 -0
  252. rucio/db/sqla/migrate_repo/versions/4783c1f49cb4_create_distance_table.py +59 -0
  253. rucio/db/sqla/migrate_repo/versions/49a21b4d4357_create_index_on_table_tokens.py +44 -0
  254. rucio/db/sqla/migrate_repo/versions/4a2cbedda8b9_add_source_replica_expression_column_to_.py +43 -0
  255. rucio/db/sqla/migrate_repo/versions/4a7182d9578b_added_bytes_length_accessed_at_columns.py +49 -0
  256. rucio/db/sqla/migrate_repo/versions/4bab9edd01fc_create_index_on_requests_rule_id.py +40 -0
  257. rucio/db/sqla/migrate_repo/versions/4c3a4acfe006_new_attr_account_table.py +63 -0
  258. rucio/db/sqla/migrate_repo/versions/4cf0a2e127d4_adding_transient_metadata.py +43 -0
  259. rucio/db/sqla/migrate_repo/versions/4df2c5ddabc0_remove_temporary_dids.py +55 -0
  260. rucio/db/sqla/migrate_repo/versions/50280c53117c_add_qos_class_to_rse.py +45 -0
  261. rucio/db/sqla/migrate_repo/versions/52153819589c_add_rse_id_to_replicas_table.py +43 -0
  262. rucio/db/sqla/migrate_repo/versions/52fd9f4916fa_added_activity_to_rules.py +43 -0
  263. rucio/db/sqla/migrate_repo/versions/53b479c3cb0f_fix_did_meta_table_missing_updated_at_.py +45 -0
  264. rucio/db/sqla/migrate_repo/versions/5673b4b6e843_add_wfms_metadata_to_rule_tables.py +47 -0
  265. rucio/db/sqla/migrate_repo/versions/575767d9f89_added_source_history_table.py +58 -0
  266. rucio/db/sqla/migrate_repo/versions/58bff7008037_add_started_at_to_requests.py +45 -0
  267. rucio/db/sqla/migrate_repo/versions/58c8b78301ab_rename_callback_to_message.py +106 -0
  268. rucio/db/sqla/migrate_repo/versions/5f139f77382a_added_child_rule_id_column.py +55 -0
  269. rucio/db/sqla/migrate_repo/versions/688ef1840840_adding_did_meta_table.py +50 -0
  270. rucio/db/sqla/migrate_repo/versions/6e572a9bfbf3_add_new_split_container_column_to_rules.py +47 -0
  271. rucio/db/sqla/migrate_repo/versions/70587619328_add_comment_column_for_subscriptions.py +43 -0
  272. rucio/db/sqla/migrate_repo/versions/739064d31565_remove_history_table_pks.py +41 -0
  273. rucio/db/sqla/migrate_repo/versions/7541902bf173_add_didsfollowed_and_followevents_table.py +91 -0
  274. rucio/db/sqla/migrate_repo/versions/7ec22226cdbf_new_replica_state_for_temporary_.py +72 -0
  275. rucio/db/sqla/migrate_repo/versions/810a41685bc1_added_columns_rse_transfer_limits.py +49 -0
  276. rucio/db/sqla/migrate_repo/versions/83f991c63a93_correct_rse_expression_length.py +43 -0
  277. rucio/db/sqla/migrate_repo/versions/8523998e2e76_increase_size_of_extended_attributes_.py +43 -0
  278. rucio/db/sqla/migrate_repo/versions/8ea9122275b1_adding_missing_function_based_indices.py +53 -0
  279. rucio/db/sqla/migrate_repo/versions/90f47792bb76_add_clob_payload_to_messages.py +45 -0
  280. rucio/db/sqla/migrate_repo/versions/914b8f02df38_new_table_for_lifetime_model_exceptions.py +68 -0
  281. rucio/db/sqla/migrate_repo/versions/94a5961ddbf2_add_estimator_columns.py +45 -0
  282. rucio/db/sqla/migrate_repo/versions/9a1b149a2044_add_saml_identity_type.py +94 -0
  283. rucio/db/sqla/migrate_repo/versions/9a45bc4ea66d_add_vp_table.py +54 -0
  284. rucio/db/sqla/migrate_repo/versions/9eb936a81eb1_true_is_true.py +72 -0
  285. rucio/db/sqla/migrate_repo/versions/a08fa8de1545_transfer_stats_table.py +55 -0
  286. rucio/db/sqla/migrate_repo/versions/a118956323f8_added_vo_table_and_vo_col_to_rse.py +76 -0
  287. rucio/db/sqla/migrate_repo/versions/a193a275255c_add_status_column_in_messages.py +47 -0
  288. rucio/db/sqla/migrate_repo/versions/a5f6f6e928a7_1_7_0.py +121 -0
  289. rucio/db/sqla/migrate_repo/versions/a616581ee47_added_columns_to_table_requests.py +59 -0
  290. rucio/db/sqla/migrate_repo/versions/a6eb23955c28_state_idx_non_functional.py +52 -0
  291. rucio/db/sqla/migrate_repo/versions/a74275a1ad30_added_global_quota_table.py +54 -0
  292. rucio/db/sqla/migrate_repo/versions/a93e4e47bda_heartbeats.py +64 -0
  293. rucio/db/sqla/migrate_repo/versions/ae2a56fcc89_added_comment_column_to_rules.py +49 -0
  294. rucio/db/sqla/migrate_repo/versions/b0070f3695c8_add_deletedidmeta_table.py +57 -0
  295. rucio/db/sqla/migrate_repo/versions/b4293a99f344_added_column_identity_to_table_tokens.py +43 -0
  296. rucio/db/sqla/migrate_repo/versions/b5493606bbf5_fix_primary_key_for_subscription_history.py +41 -0
  297. rucio/db/sqla/migrate_repo/versions/b7d287de34fd_removal_of_replicastate_source.py +91 -0
  298. rucio/db/sqla/migrate_repo/versions/b818052fa670_add_index_to_quarantined_replicas.py +40 -0
  299. rucio/db/sqla/migrate_repo/versions/b8caac94d7f0_add_comments_column_for_subscriptions_.py +43 -0
  300. rucio/db/sqla/migrate_repo/versions/b96a1c7e1cc4_new_bad_pfns_table_and_bad_replicas_.py +143 -0
  301. rucio/db/sqla/migrate_repo/versions/bb695f45c04_extend_request_state.py +76 -0
  302. rucio/db/sqla/migrate_repo/versions/bc68e9946deb_add_staging_timestamps_to_request.py +50 -0
  303. rucio/db/sqla/migrate_repo/versions/bf3baa1c1474_correct_pk_and_idx_for_history_tables.py +72 -0
  304. rucio/db/sqla/migrate_repo/versions/c0937668555f_add_qos_policy_map_table.py +55 -0
  305. rucio/db/sqla/migrate_repo/versions/c129ccdb2d5_add_lumiblocknr_to_dids.py +43 -0
  306. rucio/db/sqla/migrate_repo/versions/ccdbcd48206e_add_did_type_column_index_on_did_meta_.py +65 -0
  307. rucio/db/sqla/migrate_repo/versions/cebad904c4dd_new_payload_column_for_heartbeats.py +47 -0
  308. rucio/db/sqla/migrate_repo/versions/d1189a09c6e0_oauth2_0_and_jwt_feature_support_adding_.py +146 -0
  309. rucio/db/sqla/migrate_repo/versions/d23453595260_extend_request_state_for_preparer.py +104 -0
  310. rucio/db/sqla/migrate_repo/versions/d6dceb1de2d_added_purge_column_to_rules.py +44 -0
  311. rucio/db/sqla/migrate_repo/versions/d6e2c3b2cf26_remove_third_party_copy_column_from_rse.py +43 -0
  312. rucio/db/sqla/migrate_repo/versions/d91002c5841_new_account_limits_table.py +103 -0
  313. rucio/db/sqla/migrate_repo/versions/e138c364ebd0_extending_columns_for_filter_and_.py +49 -0
  314. rucio/db/sqla/migrate_repo/versions/e59300c8b179_support_for_archive.py +104 -0
  315. rucio/db/sqla/migrate_repo/versions/f1b14a8c2ac1_postgres_use_check_constraints.py +29 -0
  316. rucio/db/sqla/migrate_repo/versions/f41ffe206f37_oracle_global_temporary_tables.py +74 -0
  317. rucio/db/sqla/migrate_repo/versions/f85a2962b021_adding_transfertool_column_to_requests_.py +47 -0
  318. rucio/db/sqla/migrate_repo/versions/fa7a7d78b602_increase_refresh_token_size.py +43 -0
  319. rucio/db/sqla/migrate_repo/versions/fb28a95fe288_add_replicas_rse_id_tombstone_idx.py +37 -0
  320. rucio/db/sqla/migrate_repo/versions/fe1a65b176c9_set_third_party_copy_read_and_write_.py +43 -0
  321. rucio/db/sqla/migrate_repo/versions/fe8ea2fa9788_added_third_party_copy_column_to_rse_.py +43 -0
  322. rucio/db/sqla/models.py +1743 -0
  323. rucio/db/sqla/sautils.py +55 -0
  324. rucio/db/sqla/session.py +529 -0
  325. rucio/db/sqla/types.py +206 -0
  326. rucio/db/sqla/util.py +543 -0
  327. rucio/gateway/__init__.py +13 -0
  328. rucio/gateway/account.py +345 -0
  329. rucio/gateway/account_limit.py +363 -0
  330. rucio/gateway/authentication.py +381 -0
  331. rucio/gateway/config.py +227 -0
  332. rucio/gateway/credential.py +70 -0
  333. rucio/gateway/did.py +987 -0
  334. rucio/gateway/dirac.py +83 -0
  335. rucio/gateway/exporter.py +60 -0
  336. rucio/gateway/heartbeat.py +76 -0
  337. rucio/gateway/identity.py +189 -0
  338. rucio/gateway/importer.py +46 -0
  339. rucio/gateway/lifetime_exception.py +121 -0
  340. rucio/gateway/lock.py +153 -0
  341. rucio/gateway/meta_conventions.py +98 -0
  342. rucio/gateway/permission.py +74 -0
  343. rucio/gateway/quarantined_replica.py +79 -0
  344. rucio/gateway/replica.py +538 -0
  345. rucio/gateway/request.py +330 -0
  346. rucio/gateway/rse.py +632 -0
  347. rucio/gateway/rule.py +437 -0
  348. rucio/gateway/scope.py +100 -0
  349. rucio/gateway/subscription.py +280 -0
  350. rucio/gateway/vo.py +126 -0
  351. rucio/rse/__init__.py +96 -0
  352. rucio/rse/protocols/__init__.py +13 -0
  353. rucio/rse/protocols/bittorrent.py +194 -0
  354. rucio/rse/protocols/cache.py +111 -0
  355. rucio/rse/protocols/dummy.py +100 -0
  356. rucio/rse/protocols/gfal.py +708 -0
  357. rucio/rse/protocols/globus.py +243 -0
  358. rucio/rse/protocols/http_cache.py +82 -0
  359. rucio/rse/protocols/mock.py +123 -0
  360. rucio/rse/protocols/ngarc.py +209 -0
  361. rucio/rse/protocols/posix.py +250 -0
  362. rucio/rse/protocols/protocol.py +361 -0
  363. rucio/rse/protocols/rclone.py +365 -0
  364. rucio/rse/protocols/rfio.py +145 -0
  365. rucio/rse/protocols/srm.py +338 -0
  366. rucio/rse/protocols/ssh.py +414 -0
  367. rucio/rse/protocols/storm.py +195 -0
  368. rucio/rse/protocols/webdav.py +594 -0
  369. rucio/rse/protocols/xrootd.py +302 -0
  370. rucio/rse/rsemanager.py +881 -0
  371. rucio/rse/translation.py +260 -0
  372. rucio/tests/__init__.py +13 -0
  373. rucio/tests/common.py +280 -0
  374. rucio/tests/common_server.py +149 -0
  375. rucio/transfertool/__init__.py +13 -0
  376. rucio/transfertool/bittorrent.py +200 -0
  377. rucio/transfertool/bittorrent_driver.py +50 -0
  378. rucio/transfertool/bittorrent_driver_qbittorrent.py +134 -0
  379. rucio/transfertool/fts3.py +1600 -0
  380. rucio/transfertool/fts3_plugins.py +152 -0
  381. rucio/transfertool/globus.py +201 -0
  382. rucio/transfertool/globus_library.py +181 -0
  383. rucio/transfertool/mock.py +89 -0
  384. rucio/transfertool/transfertool.py +221 -0
  385. rucio/vcsversion.py +11 -0
  386. rucio/version.py +45 -0
  387. rucio/web/__init__.py +13 -0
  388. rucio/web/rest/__init__.py +13 -0
  389. rucio/web/rest/flaskapi/__init__.py +13 -0
  390. rucio/web/rest/flaskapi/authenticated_bp.py +27 -0
  391. rucio/web/rest/flaskapi/v1/__init__.py +13 -0
  392. rucio/web/rest/flaskapi/v1/accountlimits.py +236 -0
  393. rucio/web/rest/flaskapi/v1/accounts.py +1103 -0
  394. rucio/web/rest/flaskapi/v1/archives.py +102 -0
  395. rucio/web/rest/flaskapi/v1/auth.py +1644 -0
  396. rucio/web/rest/flaskapi/v1/common.py +426 -0
  397. rucio/web/rest/flaskapi/v1/config.py +304 -0
  398. rucio/web/rest/flaskapi/v1/credentials.py +213 -0
  399. rucio/web/rest/flaskapi/v1/dids.py +2340 -0
  400. rucio/web/rest/flaskapi/v1/dirac.py +116 -0
  401. rucio/web/rest/flaskapi/v1/export.py +75 -0
  402. rucio/web/rest/flaskapi/v1/heartbeats.py +127 -0
  403. rucio/web/rest/flaskapi/v1/identities.py +285 -0
  404. rucio/web/rest/flaskapi/v1/import.py +132 -0
  405. rucio/web/rest/flaskapi/v1/lifetime_exceptions.py +312 -0
  406. rucio/web/rest/flaskapi/v1/locks.py +358 -0
  407. rucio/web/rest/flaskapi/v1/main.py +91 -0
  408. rucio/web/rest/flaskapi/v1/meta_conventions.py +241 -0
  409. rucio/web/rest/flaskapi/v1/metrics.py +36 -0
  410. rucio/web/rest/flaskapi/v1/nongrid_traces.py +97 -0
  411. rucio/web/rest/flaskapi/v1/ping.py +88 -0
  412. rucio/web/rest/flaskapi/v1/redirect.py +366 -0
  413. rucio/web/rest/flaskapi/v1/replicas.py +1894 -0
  414. rucio/web/rest/flaskapi/v1/requests.py +998 -0
  415. rucio/web/rest/flaskapi/v1/rses.py +2250 -0
  416. rucio/web/rest/flaskapi/v1/rules.py +854 -0
  417. rucio/web/rest/flaskapi/v1/scopes.py +159 -0
  418. rucio/web/rest/flaskapi/v1/subscriptions.py +650 -0
  419. rucio/web/rest/flaskapi/v1/templates/auth_crash.html +80 -0
  420. rucio/web/rest/flaskapi/v1/templates/auth_granted.html +82 -0
  421. rucio/web/rest/flaskapi/v1/traces.py +137 -0
  422. rucio/web/rest/flaskapi/v1/types.py +20 -0
  423. rucio/web/rest/flaskapi/v1/vos.py +278 -0
  424. rucio/web/rest/main.py +18 -0
  425. rucio/web/rest/metrics.py +27 -0
  426. rucio/web/rest/ping.py +27 -0
  427. rucio-37.0.0rc1.data/data/rucio/etc/alembic.ini.template +71 -0
  428. rucio-37.0.0rc1.data/data/rucio/etc/alembic_offline.ini.template +74 -0
  429. rucio-37.0.0rc1.data/data/rucio/etc/globus-config.yml.template +5 -0
  430. rucio-37.0.0rc1.data/data/rucio/etc/ldap.cfg.template +30 -0
  431. rucio-37.0.0rc1.data/data/rucio/etc/mail_templates/rule_approval_request.tmpl +38 -0
  432. rucio-37.0.0rc1.data/data/rucio/etc/mail_templates/rule_approved_admin.tmpl +4 -0
  433. rucio-37.0.0rc1.data/data/rucio/etc/mail_templates/rule_approved_user.tmpl +17 -0
  434. rucio-37.0.0rc1.data/data/rucio/etc/mail_templates/rule_denied_admin.tmpl +6 -0
  435. rucio-37.0.0rc1.data/data/rucio/etc/mail_templates/rule_denied_user.tmpl +17 -0
  436. rucio-37.0.0rc1.data/data/rucio/etc/mail_templates/rule_ok_notification.tmpl +19 -0
  437. rucio-37.0.0rc1.data/data/rucio/etc/rse-accounts.cfg.template +25 -0
  438. rucio-37.0.0rc1.data/data/rucio/etc/rucio.cfg.atlas.client.template +43 -0
  439. rucio-37.0.0rc1.data/data/rucio/etc/rucio.cfg.template +241 -0
  440. rucio-37.0.0rc1.data/data/rucio/etc/rucio_multi_vo.cfg.template +217 -0
  441. rucio-37.0.0rc1.data/data/rucio/requirements.server.txt +297 -0
  442. rucio-37.0.0rc1.data/data/rucio/tools/bootstrap.py +34 -0
  443. rucio-37.0.0rc1.data/data/rucio/tools/merge_rucio_configs.py +144 -0
  444. rucio-37.0.0rc1.data/data/rucio/tools/reset_database.py +40 -0
  445. rucio-37.0.0rc1.data/scripts/rucio +133 -0
  446. rucio-37.0.0rc1.data/scripts/rucio-abacus-account +74 -0
  447. rucio-37.0.0rc1.data/scripts/rucio-abacus-collection-replica +46 -0
  448. rucio-37.0.0rc1.data/scripts/rucio-abacus-rse +78 -0
  449. rucio-37.0.0rc1.data/scripts/rucio-admin +97 -0
  450. rucio-37.0.0rc1.data/scripts/rucio-atropos +60 -0
  451. rucio-37.0.0rc1.data/scripts/rucio-auditor +206 -0
  452. rucio-37.0.0rc1.data/scripts/rucio-automatix +50 -0
  453. rucio-37.0.0rc1.data/scripts/rucio-bb8 +57 -0
  454. rucio-37.0.0rc1.data/scripts/rucio-cache-client +141 -0
  455. rucio-37.0.0rc1.data/scripts/rucio-cache-consumer +42 -0
  456. rucio-37.0.0rc1.data/scripts/rucio-conveyor-finisher +58 -0
  457. rucio-37.0.0rc1.data/scripts/rucio-conveyor-poller +66 -0
  458. rucio-37.0.0rc1.data/scripts/rucio-conveyor-preparer +37 -0
  459. rucio-37.0.0rc1.data/scripts/rucio-conveyor-receiver +44 -0
  460. rucio-37.0.0rc1.data/scripts/rucio-conveyor-stager +76 -0
  461. rucio-37.0.0rc1.data/scripts/rucio-conveyor-submitter +139 -0
  462. rucio-37.0.0rc1.data/scripts/rucio-conveyor-throttler +104 -0
  463. rucio-37.0.0rc1.data/scripts/rucio-dark-reaper +53 -0
  464. rucio-37.0.0rc1.data/scripts/rucio-dumper +160 -0
  465. rucio-37.0.0rc1.data/scripts/rucio-follower +44 -0
  466. rucio-37.0.0rc1.data/scripts/rucio-hermes +54 -0
  467. rucio-37.0.0rc1.data/scripts/rucio-judge-cleaner +89 -0
  468. rucio-37.0.0rc1.data/scripts/rucio-judge-evaluator +137 -0
  469. rucio-37.0.0rc1.data/scripts/rucio-judge-injector +44 -0
  470. rucio-37.0.0rc1.data/scripts/rucio-judge-repairer +44 -0
  471. rucio-37.0.0rc1.data/scripts/rucio-kronos +44 -0
  472. rucio-37.0.0rc1.data/scripts/rucio-minos +53 -0
  473. rucio-37.0.0rc1.data/scripts/rucio-minos-temporary-expiration +50 -0
  474. rucio-37.0.0rc1.data/scripts/rucio-necromancer +120 -0
  475. rucio-37.0.0rc1.data/scripts/rucio-oauth-manager +63 -0
  476. rucio-37.0.0rc1.data/scripts/rucio-reaper +83 -0
  477. rucio-37.0.0rc1.data/scripts/rucio-replica-recoverer +248 -0
  478. rucio-37.0.0rc1.data/scripts/rucio-rse-decommissioner +66 -0
  479. rucio-37.0.0rc1.data/scripts/rucio-storage-consistency-actions +74 -0
  480. rucio-37.0.0rc1.data/scripts/rucio-transmogrifier +77 -0
  481. rucio-37.0.0rc1.data/scripts/rucio-undertaker +76 -0
  482. rucio-37.0.0rc1.dist-info/METADATA +92 -0
  483. rucio-37.0.0rc1.dist-info/RECORD +487 -0
  484. rucio-37.0.0rc1.dist-info/WHEEL +5 -0
  485. rucio-37.0.0rc1.dist-info/licenses/AUTHORS.rst +100 -0
  486. rucio-37.0.0rc1.dist-info/licenses/LICENSE +201 -0
  487. rucio-37.0.0rc1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,532 @@
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
+ Conveyor throttler is a daemon to manage rucio internal queue.
17
+ """
18
+ import logging
19
+ import math
20
+ import threading
21
+ import traceback
22
+ from collections import defaultdict
23
+ from typing import TYPE_CHECKING, Optional, TypedDict, Union
24
+
25
+ from sqlalchemy import null
26
+
27
+ import rucio.db.sqla.util
28
+ from rucio.common import exception
29
+ from rucio.common.logging import setup_logging
30
+ from rucio.core.monitor import MetricManager
31
+ from rucio.core.request import get_request_stats, re_sync_all_transfer_limits, release_all_waiting_requests, release_waiting_requests_fifo, release_waiting_requests_grouped_fifo, reset_stale_waiting_requests, set_transfer_limit_stats
32
+ from rucio.core.rse import RseCollection, RseData
33
+ from rucio.core.transfer import applicable_rse_transfer_limits
34
+ from rucio.daemons.common import ProducerConsumerDaemon, db_workqueue
35
+ from rucio.db.sqla.constants import RequestState, TransferLimitDirection
36
+
37
+ if TYPE_CHECKING:
38
+ from collections.abc import Iterator
39
+ from types import FrameType
40
+
41
+ from rucio.common.types import InternalAccount, LoggerFunction
42
+ from rucio.daemons.common import HeartbeatHandler
43
+
44
+ class LimitDict(TypedDict):
45
+ activity: Optional[str]
46
+ direction: TransferLimitDirection
47
+ waitings: int
48
+ transfers: int
49
+ max_transfers: int
50
+ rse_expression: str
51
+ strategy: str
52
+ volume: int
53
+ deadline: int
54
+
55
+ class AccountStatDict(TypedDict):
56
+ waiting: int
57
+ active: int
58
+ residual_capacity: float
59
+
60
+ class StatDict(TypedDict):
61
+ accounts: AccountStatDict
62
+ residual_capacity: float
63
+ waiting: int
64
+ active: int
65
+
66
+ class LimitStatDict(TypedDict):
67
+ limit: LimitDict
68
+ stat: StatDict
69
+
70
+ ReleaseGroupsDict = dict[
71
+ tuple[
72
+ RseData, # source_rse
73
+ RseData, # dest_rse
74
+ str, # activity
75
+ ],
76
+ list[LimitStatDict] # applicable_limits
77
+ ]
78
+
79
+
80
+ GRACEFUL_STOP = threading.Event()
81
+ METRICS = MetricManager(module=__name__)
82
+ DAEMON_NAME = 'conveyor-throttler'
83
+
84
+
85
+ def throttler(
86
+ once: bool = False,
87
+ sleep_time: int = 600,
88
+ partition_wait_time: int = 10
89
+ ) -> None:
90
+ """
91
+ Main loop to check rse transfer limits.
92
+ """
93
+
94
+ logging.info('Throttler starting')
95
+
96
+ @db_workqueue(
97
+ once=once,
98
+ graceful_stop=GRACEFUL_STOP,
99
+ executable=DAEMON_NAME,
100
+ partition_wait_time=partition_wait_time,
101
+ sleep_time=sleep_time)
102
+ def _db_producer(
103
+ *,
104
+ activity: str,
105
+ heartbeat_handler: "HeartbeatHandler"
106
+ ) -> tuple[bool, Optional["ReleaseGroupsDict"]]:
107
+ worker_number, total_workers, logger = heartbeat_handler.live()
108
+ if worker_number != 0:
109
+ logger(logging.INFO, 'Throttler thread id is not 0, will sleep. Only thread 0 will work')
110
+ return True, None
111
+
112
+ re_sync_all_transfer_limits()
113
+ rse_collection = RseCollection()
114
+ release_groups = _get_request_stats(rse_collection, logger=logger)
115
+ return True, release_groups
116
+
117
+ def _consumer(release_groups: Optional["ReleaseGroupsDict"]) -> None:
118
+ if release_groups is None:
119
+ return
120
+ logger = logging.log
121
+ logger(logging.INFO, "Throttler - schedule requests")
122
+ try:
123
+ _handle_requests(release_groups, logger=logger)
124
+ except Exception:
125
+ logger(logging.CRITICAL, "Failed to schedule requests, error: %s" % (traceback.format_exc()))
126
+ reset_stale_waiting_requests()
127
+
128
+ ProducerConsumerDaemon(
129
+ producers=[_db_producer],
130
+ consumers=[_consumer],
131
+ graceful_stop=GRACEFUL_STOP,
132
+ ).run()
133
+
134
+
135
+ def stop(signum: Optional[int] = None, frame: Optional["FrameType"] = None) -> None:
136
+ """
137
+ Graceful exit.
138
+ """
139
+
140
+ GRACEFUL_STOP.set()
141
+
142
+
143
+ def run(
144
+ once: bool = False,
145
+ sleep_time: int = 600
146
+ ) -> None:
147
+ """
148
+ Starts up the conveyor threads.
149
+ """
150
+ setup_logging(process_name=DAEMON_NAME)
151
+
152
+ if rucio.db.sqla.util.is_old_db():
153
+ raise exception.DatabaseException('Database was not updated, daemon won\'t start')
154
+
155
+ throttler(once=once, sleep_time=sleep_time)
156
+
157
+
158
+ class RequestGrouper:
159
+
160
+ class RseStatistic:
161
+ def __init__(self):
162
+ self.sources_with_limits: set[RseData] = set()
163
+ self.destinations_with_limits: set[RseData] = set()
164
+ self.unavailable_sources: set[RseData] = set()
165
+ self.unavailable_destinations: set[RseData] = set()
166
+ self.has_any_per_activity_limit = False
167
+ self.any_source_has_per_activity_limit = False
168
+ self.any_destination_has_per_activity_limit = False
169
+
170
+ def __init__(self):
171
+ self.waiting_transfer_groups: 'ReleaseGroupsDict' = {}
172
+ self.rse_stats = defaultdict(self.RseStatistic)
173
+
174
+ def record_waiting_request_group(
175
+ self,
176
+ source_rse: RseData,
177
+ dest_rse: RseData,
178
+ activity: str,
179
+ applicable_limits: list['LimitStatDict']
180
+ ) -> None:
181
+ """
182
+ Record a group of requests in waiting state, while computing some statistics about them.
183
+ """
184
+
185
+ src_info = self.rse_stats[source_rse]
186
+ dst_info = self.rse_stats[dest_rse]
187
+
188
+ if dest_rse and not dest_rse.columns['availability_write']:
189
+ src_info.unavailable_destinations.add(dest_rse)
190
+ if source_rse and not source_rse.columns['availability_read']:
191
+ dst_info.unavailable_sources.add(source_rse)
192
+ for limit_stat in applicable_limits:
193
+ limit = limit_stat['limit']
194
+ limit_is_per_activity = limit['activity'] is not None
195
+ if source_rse and limit['direction'] == TransferLimitDirection.SOURCE:
196
+ src_info.has_any_per_activity_limit |= limit_is_per_activity
197
+ dst_info.sources_with_limits.add(source_rse)
198
+ dst_info.any_source_has_per_activity_limit |= limit_is_per_activity
199
+ else:
200
+ dst_info.has_any_per_activity_limit |= limit_is_per_activity
201
+ src_info.destinations_with_limits.add(dest_rse)
202
+ src_info.any_destination_has_per_activity_limit |= limit_is_per_activity
203
+
204
+ self.waiting_transfer_groups[source_rse, dest_rse, activity] = applicable_limits
205
+
206
+ def merged_groups(self) -> "ReleaseGroupsDict":
207
+ """
208
+ Merge groups which can be handled together
209
+ """
210
+
211
+ merged_groups: "ReleaseGroupsDict" = {}
212
+ for (source_rse, dest_rse, activity), applicable_limits in self.waiting_transfer_groups.items():
213
+
214
+ src_info = self.rse_stats[source_rse]
215
+ dst_info = self.rse_stats[dest_rse]
216
+
217
+ if not dst_info.sources_with_limits and not dst_info.unavailable_sources:
218
+ # None of the RSEs used as sources towards dest_rse has a limit configured
219
+ # And none of them is unavailable.
220
+ # It is possible to handle all waiting requests from any source in a single bulk
221
+ source_rse = None
222
+
223
+ if source_rse and not src_info.destinations_with_limits and not src_info.unavailable_destinations:
224
+ # All waiting requests from this source to any of the destination rses can be handled together
225
+ dest_rse = None
226
+
227
+ if source_rse and dest_rse:
228
+ if not dst_info.has_any_per_activity_limit and not src_info.has_any_per_activity_limit:
229
+ # All limits for this source/destination pair are configured per "all_activities",
230
+ # We can thus handle all activities in a single bulk.
231
+ activity = None
232
+ elif dest_rse:
233
+ if not dst_info.has_any_per_activity_limit and not dst_info.any_source_has_per_activity_limit:
234
+ # All limits, on all sources est_rse are configured
235
+ # per "all_activities". We don't need to handle each activity separately.
236
+ activity = None
237
+ elif source_rse:
238
+ if not src_info.has_any_per_activity_limit and not src_info.any_destination_has_per_activity_limit:
239
+ activity = None
240
+
241
+ merged_groups.setdefault((source_rse, dest_rse, activity), applicable_limits) # type: ignore
242
+
243
+ return merged_groups
244
+
245
+
246
+ def _get_request_stats(
247
+ rse_collection: RseCollection,
248
+ *,
249
+ logger: "LoggerFunction" = logging.log
250
+ ) -> "ReleaseGroupsDict":
251
+ """
252
+ Group waiting requests into arbitrary groups for bulk handling.
253
+ The current grouping (source rse + dest rse + activity) was dictated
254
+ by SQL queries used to release requests later in the throttler.
255
+ Any combination of the group attributes can be None. For example,
256
+ if activity and source rse are None, later code will work on all
257
+ requests towards their common destination rse in one go.
258
+
259
+ For each group, find the limits which apply to that group. The same
260
+ limit can be shared by multiple groups.
261
+
262
+ For each limit, compute the total number of active and waiting transfers
263
+ subject to that limit.
264
+ """
265
+ logging.info("Throttler retrieve requests statistics")
266
+
267
+ db_stats = get_request_stats( # type: ignore (Session parameter is missing)
268
+ state=[RequestState.QUEUED,
269
+ RequestState.SUBMITTING,
270
+ RequestState.SUBMITTED,
271
+ RequestState.WAITING],
272
+ )
273
+
274
+ # for each active limit, compute how many waiting and active transfers are currently in the database
275
+ limit_stats = {}
276
+ # for each group of (source_rse, destination_rse, activity) of waiting requests, find the limits which must be enforced
277
+ grouper = RequestGrouper()
278
+ for db_stat in db_stats:
279
+ account = db_stat.account
280
+ state = db_stat.state
281
+ counter = db_stat.counter
282
+ activity = db_stat.activity
283
+
284
+ try:
285
+ dest_rse = rse_collection[db_stat.dest_rse_id].ensure_loaded(load_transfer_limits=True, load_name=True, load_columns=True)
286
+ except exception.RSENotFound:
287
+ logger(logging.INFO, "Destination RSE {} not found. Probably deleted.", db_stat.dest_rse_id)
288
+ continue
289
+ source_rse = None
290
+ if db_stat.source_rse_id:
291
+ try:
292
+ source_rse = rse_collection[db_stat.source_rse_id].ensure_loaded(load_transfer_limits=True, load_name=True, load_columns=True)
293
+ except exception.RSENotFound:
294
+ logger(logging.INFO, "Source RSE {} not found. Probably deleted.", db_stat.source_rse_id)
295
+ continue
296
+
297
+ source_limits = list(applicable_rse_transfer_limits(activity=activity, source_rse=source_rse))
298
+ dest_limits = list(applicable_rse_transfer_limits(activity=activity, dest_rse=dest_rse))
299
+ limits = source_limits + dest_limits
300
+
301
+ if counter and (limits or state == RequestState.WAITING):
302
+ applicable_limits = []
303
+ for limit in limits:
304
+ limit_stat = limit_stats.setdefault(limit['id'], {})
305
+ applicable_limits.append(limit_stat)
306
+
307
+ if not limit_stat:
308
+ limit_stat.update({
309
+ 'limit': limit,
310
+ 'stat': {
311
+ 'waiting': 0,
312
+ 'active': 0,
313
+ 'accounts': {},
314
+ }
315
+ })
316
+
317
+ if account is None:
318
+ # account == None results in SQL queries which doesn't filter on account at all.
319
+ # While account == null() explicitly filters on "account is NULL" in the database.
320
+ # Here we want the second case.
321
+ account = null()
322
+
323
+ stat = limit_stat['stat']
324
+ if account not in stat['accounts']:
325
+ stat['accounts'][account] = {'waiting': 0, 'active': 0}
326
+
327
+ if state == RequestState.WAITING:
328
+ stat['waiting'] += counter
329
+ stat['accounts'][account]['waiting'] += counter
330
+ else:
331
+ stat['active'] += counter
332
+ stat['accounts'][account]['active'] += counter
333
+
334
+ if state == RequestState.WAITING:
335
+ grouper.record_waiting_request_group(
336
+ source_rse=source_rse, # type: ignore
337
+ dest_rse=dest_rse,
338
+ activity=activity,
339
+ applicable_limits=applicable_limits,
340
+ )
341
+
342
+ # Find the residual capacity in each of the limits
343
+ for limit_stat in limit_stats.values():
344
+ stat = limit_stat['stat']
345
+ limit = limit_stat['limit']
346
+
347
+ waiting = stat['waiting']
348
+ active = stat['active']
349
+
350
+ max_transfers = limit['max_transfers']
351
+
352
+ rse_expression = limit['rse_expression']
353
+ log_str = f'limit {"from" if limit["direction"] == "source" else "to"} {rse_expression} activity {limit["activity"]}'
354
+
355
+ if max_transfers is None:
356
+ # The limit was explicitly set to NULL. Release all waiting requests.
357
+ residual_capacity = math.inf
358
+ elif active < 0.8 * max_transfers:
359
+ residual_capacity = max_transfers - active
360
+ elif 0.8 * max_transfers <= active < max_transfers:
361
+ # Don't release requests yet. We desire to release transfers in bigger bulks than the currently available capacity.
362
+ logger(logging.DEBUG, "%s: will do nothing (active >= 0.8 * max_transfers)", log_str)
363
+ residual_capacity = 0
364
+ else: # active >= max_transfers
365
+ residual_capacity = 0
366
+ stat['residual_capacity'] = residual_capacity
367
+
368
+ activity = limit['activity'] or 'all_activities'
369
+ METRICS.gauge('rse_transfer_limits.{activity}.{rse}.{limit_attr}').labels(activity=activity, rse=rse_expression, limit_attr='residual_capacity').set(residual_capacity)
370
+ METRICS.gauge('rse_transfer_limits.{activity}.{rse}.{limit_attr}').labels(activity=activity, rse=rse_expression, limit_attr='max_transfers').set(max_transfers)
371
+ METRICS.gauge('rse_transfer_limits.{activity}.{rse}.{limit_attr}').labels(activity=activity, rse=rse_expression, limit_attr='active').set(active)
372
+ METRICS.gauge('rse_transfer_limits.{activity}.{rse}.{limit_attr}').labels(activity=activity, rse=rse_expression, limit_attr='waiting').set(waiting)
373
+
374
+ if waiting:
375
+ logger(logging.DEBUG, "%s: can release %s out of %s waiting requests", log_str, residual_capacity, waiting)
376
+
377
+ if waiting != limit['waitings'] or active != limit['transfers']:
378
+ set_transfer_limit_stats(limit['id'], waitings=waiting, transfers=active)
379
+
380
+ for account, to_release_for_account in _split_threshold_per_account(stat['accounts'], total_to_release=residual_capacity): # type: ignore (stat['accounts'] is not None)
381
+ stat['accounts'][account]['residual_capacity'] = to_release_for_account
382
+
383
+ release_groups = grouper.merged_groups()
384
+ return release_groups
385
+
386
+
387
+ def _split_threshold_per_account(
388
+ per_account_stats: dict["InternalAccount", dict[str, float]],
389
+ total_to_release: float
390
+ ) -> Union[tuple[None, int], "Iterator[tuple[InternalAccount, float]]"]:
391
+ """
392
+ Compute how many requests to release for each account. Try to achieve a fair share of transfers between accounts.
393
+ :param per_account_stats: a dict with how many active and waiting transfers each account has
394
+ :param total_to_release: the total threshold allowed to be released
395
+ :return: for each account, how many requests to release
396
+ """
397
+
398
+ if not per_account_stats:
399
+ return None, total_to_release
400
+
401
+ nr_accounts = len(per_account_stats)
402
+ remaining_to_release = total_to_release
403
+ remaining_accounts = nr_accounts
404
+ for account, account_stat in sorted(per_account_stats.items(), key=lambda i: i[1]['waiting']):
405
+ threshold_per_account = math.ceil(remaining_to_release / remaining_accounts)
406
+
407
+ waiting = account_stat['waiting']
408
+ to_release_for_account = min(waiting, threshold_per_account)
409
+
410
+ yield account, to_release_for_account
411
+
412
+ remaining_accounts -= 1
413
+ remaining_to_release -= to_release_for_account
414
+
415
+
416
+ def _combine_limits(
417
+ applicable_limits: list['LimitStatDict']
418
+ ) -> tuple[float, str, Optional[int], Optional[int]]:
419
+ """
420
+ Take multiple limits and combines them into one single (strictest) limit which
421
+ respects the constraints of each initial limits. This is to handle cases like:
422
+ - source rse only allows 5 transfers; destination allows 10 -> keep the stricter limit (here: 5 transfers)
423
+ - an RSE has multiple limits due to overlapping rse expressions -> also keep the stricter limit
424
+
425
+ Rules:
426
+ - for `to_release`: pick the minimum available residual capacity
427
+ - for `max_transfers` and 'volume': just pick the minimum
428
+ - prioritize `grouped_fifo` strategy over `fifo` (fifo being the default when not set)
429
+ - keep the closest deadline
430
+ """
431
+ strategy_priorities = {
432
+ 'grouped_fifo': 1,
433
+ 'fifo': 2,
434
+ None: 3,
435
+ }
436
+ to_release = math.inf
437
+ max_transfers = None
438
+ strategy = 'fifo'
439
+ volume = None
440
+ deadline = None
441
+ for limit_stat in applicable_limits:
442
+ limit = limit_stat['limit']
443
+ stat = limit_stat['stat']
444
+
445
+ to_release = min(stat['residual_capacity'], to_release)
446
+ max_transfers = min(limit['max_transfers'], max_transfers, key=lambda x: x if x is not None else math.inf)
447
+ strategy = min(limit['strategy'], strategy, key=lambda x: strategy_priorities.get(x, math.inf))
448
+ volume = min(limit['volume'], volume, key=lambda x: x if x is not None else math.inf)
449
+ deadline = min(limit['deadline'], deadline, key=lambda x: x if x is not None else math.inf)
450
+
451
+ return to_release, strategy, volume, deadline
452
+
453
+
454
+ def _handle_requests(
455
+ release_groups: "ReleaseGroupsDict",
456
+ logger: "LoggerFunction"
457
+ ) -> None:
458
+ """
459
+ Release (set to queued state) waiting requests in groups defined by release_groups.
460
+
461
+ The same limit can be shared by multiple groups. Because of that, releasing requests
462
+ from one group can impact how many requests may be released in other groups subjected
463
+ to the same limit.
464
+ """
465
+
466
+ for (source_rse, dest_rse, activity), applicable_limits in release_groups.items():
467
+
468
+ # Skip if dest_rse is blocklisted for write or src_rse is blocklisted for read
469
+ if dest_rse and not dest_rse.columns['availability_write']:
470
+ continue
471
+ if source_rse and not source_rse.columns['availability_read']:
472
+ continue
473
+
474
+ source_rse_id = source_rse.id if source_rse else None
475
+ dest_rse_id = dest_rse.id if dest_rse else None
476
+
477
+ log_str = (f' for activity "{activity}"' if activity else '') + \
478
+ (f' from rse {source_rse}' if source_rse else '') + \
479
+ (f' to rse {dest_rse}' if dest_rse else '')
480
+
481
+ to_release, strategy, volume, deadline = _combine_limits(applicable_limits)
482
+ if not to_release:
483
+ logger(logging.DEBUG, "no requests can be released%s", log_str)
484
+ total_released = 0
485
+ elif to_release == math.inf:
486
+ logger(logging.DEBUG, "will release all waiting requests%s", log_str)
487
+ total_released = release_all_waiting_requests(dest_rse_id=dest_rse_id, source_rse_id=source_rse_id, activity=activity)
488
+ elif strategy == 'grouped_fifo':
489
+ logger(logging.DEBUG, "will release %s remaining requests%s", to_release, log_str)
490
+ additional_kwargs = {}
491
+ if volume is not None:
492
+ additional_kwargs['volume'] = volume
493
+ if deadline is not None:
494
+ additional_kwargs['deadline'] = deadline
495
+ total_released = release_waiting_requests_grouped_fifo(
496
+ source_rse_id=source_rse_id,
497
+ dest_rse_id=dest_rse_id,
498
+ count=to_release,
499
+ **additional_kwargs,
500
+ )
501
+ else:
502
+ total_released = 0
503
+ to_release_for_account = {}
504
+ limits_by_account = {}
505
+ for limit_stat in applicable_limits:
506
+ acc_dict = limit_stat['stat']['accounts']
507
+ for account, account_limit in acc_dict.items():
508
+ to_release_for_account[account] = min(to_release_for_account.get(account, to_release), account_limit['residual_capacity']) # type: ignore (Issue with TypedDict.__getitem__)
509
+ limits_by_account.setdefault(account, []).append(account_limit)
510
+
511
+ for account, to_release_account in to_release_for_account.items():
512
+ if not to_release_account:
513
+ continue
514
+
515
+ logger(logging.DEBUG, 'releasing %s waiting requests%s%s', to_release_account, log_str, f' account {account}' if account is not None else '')
516
+ nb_released = release_waiting_requests_fifo(
517
+ source_rse_id=source_rse_id,
518
+ dest_rse_id=dest_rse_id,
519
+ count=to_release_account,
520
+ activity=activity,
521
+ account=account,
522
+ )
523
+ total_released += nb_released
524
+
525
+ for stat in limits_by_account[account]:
526
+ stat['residual_capacity'] -= nb_released
527
+
528
+ if total_released:
529
+ for limit_stat in applicable_limits:
530
+ rse_expression = limit_stat['limit']['rse_expression']
531
+ limit_stat['stat']['residual_capacity'] -= total_released
532
+ METRICS.counter('released_waiting_requests.{activity}.{rse}').labels(activity=activity, rse=rse_expression).inc(total_released)
@@ -0,0 +1,13 @@
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.
@@ -0,0 +1,101 @@
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
+ import os
17
+ import socket
18
+ import threading
19
+ import time
20
+ from typing import TYPE_CHECKING
21
+
22
+ import rucio.db.sqla.util
23
+ from rucio.common import exception
24
+ from rucio.common.logging import setup_logging
25
+ from rucio.common.utils import get_thread_with_periodic_running_function
26
+ from rucio.core.did import create_reports
27
+ from rucio.core.heartbeat import die, live, sanity_check
28
+
29
+ if TYPE_CHECKING:
30
+ from types import FrameType
31
+ from typing import Optional
32
+
33
+ graceful_stop = threading.Event()
34
+ DAEMON_NAME = 'rucio-follower'
35
+
36
+
37
+ def aggregate_events(
38
+ once: bool = False
39
+ ) -> None:
40
+ """
41
+ Collect all the events affecting the dids followed by the corresponding account.
42
+ """
43
+
44
+ logging.info('event_aggregation: started')
45
+
46
+ hostname = socket.gethostname()
47
+ pid = os.getpid()
48
+ current_thread = threading.current_thread()
49
+ live(executable=DAEMON_NAME, hostname=hostname, pid=pid, thread=current_thread)
50
+
51
+ while not graceful_stop.is_set():
52
+ heartbeat = live(executable=DAEMON_NAME, hostname=hostname, pid=pid, thread=current_thread)
53
+ # Create a report of events and send a mail to the corresponding account.
54
+ start_time = time.time()
55
+ create_reports(total_workers=heartbeat['nr_threads'] - 1,
56
+ worker_number=heartbeat['assign_thread'])
57
+ logging.info('worker[%s/%s] took %s for creating reports' % (heartbeat['assign_thread'], heartbeat['nr_threads'] - 1, time.time() - start_time))
58
+
59
+ if once:
60
+ break
61
+
62
+ logging.info('follower: graceful stop requested')
63
+ die(executable=DAEMON_NAME, hostname=hostname, pid=pid, thread=current_thread)
64
+ logging.info('follower: graceful stop done')
65
+
66
+
67
+ def stop(signum: "Optional[int]" = None, frame: "Optional[FrameType]" = None) -> None:
68
+ """
69
+ Graceful exit.
70
+ """
71
+ graceful_stop.set()
72
+
73
+
74
+ def run(
75
+ once: bool = False,
76
+ threads: int = 1
77
+ ) -> None:
78
+ """
79
+ Starts up the follower threads
80
+ """
81
+ setup_logging(process_name=DAEMON_NAME)
82
+
83
+ if rucio.db.sqla.util.is_old_db():
84
+ raise exception.DatabaseException('Database was not updated, daemon won\'t start')
85
+
86
+ hostname = socket.gethostname()
87
+ sanity_check(executable=DAEMON_NAME, hostname=hostname)
88
+
89
+ if once:
90
+ logging.info("executing one follower iteration only")
91
+ aggregate_events(once)
92
+ else:
93
+ logging.info("starting follower threads")
94
+ # Run the follower daemon thrice a day
95
+ thread_list = [get_thread_with_periodic_running_function(28800, aggregate_events, graceful_stop) for i in range(threads)]
96
+ [t.start() for t in thread_list]
97
+
98
+ logging.info("waiting for interrupts")
99
+ # Interruptible joins require a timeout.
100
+ while thread_list[0].is_alive():
101
+ [t.join(timeout=3.14) for t in thread_list]
@@ -0,0 +1,13 @@
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.