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
rucio/core/transfer.py ADDED
@@ -0,0 +1,1233 @@
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 datetime
17
+ import logging
18
+ import re
19
+ import time
20
+ import traceback
21
+ from typing import TYPE_CHECKING
22
+
23
+ from dogpile.cache import make_region
24
+ from dogpile.cache.api import NoValue
25
+ from sqlalchemy import select, update
26
+ from sqlalchemy.exc import IntegrityError
27
+
28
+ from rucio.common import constants
29
+ from rucio.common.config import config_get
30
+ from rucio.common.constants import SUPPORTED_PROTOCOLS
31
+ from rucio.common.exception import (InvalidRSEExpression,
32
+ RequestNotFound, RSEProtocolNotSupported,
33
+ RucioException, UnsupportedOperation)
34
+ from rucio.common.utils import construct_surl
35
+ from rucio.core import did, message as message_core, request as request_core
36
+ from rucio.core.account import list_accounts
37
+ from rucio.core.monitor import MetricManager
38
+ from rucio.core.request import set_request_state, RequestWithSources, RequestSource
39
+ from rucio.core.rse import RseData
40
+ from rucio.core.rse_expression_parser import parse_expression
41
+ from rucio.db.sqla import models
42
+ from rucio.db.sqla.constants import DIDType, RequestState, RequestType, TransferLimitDirection
43
+ from rucio.db.sqla.session import read_session, transactional_session, stream_session
44
+ from rucio.rse import rsemanager as rsemgr
45
+ from rucio.transfertool.transfertool import TransferStatusReport
46
+ from rucio.transfertool.fts3 import FTS3Transfertool
47
+ from rucio.transfertool.globus import GlobusTransferTool
48
+ from rucio.transfertool.mock import MockTransfertool
49
+
50
+ if TYPE_CHECKING:
51
+ from collections.abc import Callable, Generator, Iterable
52
+ from typing import Any, Optional
53
+ from sqlalchemy.orm import Session
54
+ from rucio.common.types import InternalAccount
55
+ from rucio.core.topology import Topology
56
+
57
+ LoggerFunction = Callable[..., Any]
58
+
59
+ """
60
+ The core transfer.py is specifically for handling transfer-requests, thus requests
61
+ where the external_id is already known.
62
+ Requests accessed by request_id are covered in the core request.py
63
+ """
64
+
65
+ REGION_ACCOUNTS = make_region().configure('dogpile.cache.memory', expiration_time=600)
66
+ METRICS = MetricManager(module=__name__)
67
+
68
+ WEBDAV_TRANSFER_MODE = config_get('conveyor', 'webdav_transfer_mode', False, None)
69
+
70
+ DEFAULT_MULTIHOP_TOMBSTONE_DELAY = int(datetime.timedelta(hours=2).total_seconds())
71
+
72
+ TRANSFERTOOL_CLASSES_BY_NAME = {
73
+ FTS3Transfertool.external_name: FTS3Transfertool,
74
+ GlobusTransferTool.external_name: GlobusTransferTool,
75
+ MockTransfertool.external_name: MockTransfertool,
76
+ }
77
+
78
+
79
+ class TransferDestination:
80
+ def __init__(self, rse_data, scheme):
81
+ self.rse = rse_data
82
+ self.scheme = scheme
83
+
84
+ def __str__(self):
85
+ return "dst_rse={}".format(self.rse)
86
+
87
+
88
+ class ProtocolFactory:
89
+ """
90
+ Creates and caches protocol objects. Allowing to reuse them.
91
+ """
92
+ def __init__(self):
93
+ self.protocols = {}
94
+
95
+ def protocol(self, rse_data, scheme, operation):
96
+ protocol_key = '%s_%s_%s' % (operation, rse_data.id, scheme)
97
+ protocol = self.protocols.get(protocol_key)
98
+ if not protocol:
99
+ protocol = rsemgr.create_protocol(rse_data.info, operation, scheme)
100
+ self.protocols[protocol_key] = protocol
101
+ return protocol
102
+
103
+
104
+ class DirectTransferDefinition:
105
+ """
106
+ The configuration for a direct (non-multi-hop) transfer. It can be a multi-source transfer.
107
+
108
+ The class wraps the legacy dict-based transfer definition to maintain compatibility with existing code
109
+ during the migration.
110
+ """
111
+ def __init__(self, source: RequestSource, destination: TransferDestination, rws: RequestWithSources,
112
+ protocol_factory: ProtocolFactory, operation_src: str, operation_dest: str):
113
+ self.sources = [source]
114
+ self.destination = destination
115
+
116
+ self.rws = rws
117
+ self.protocol_factory = protocol_factory
118
+ self.operation_src = operation_src
119
+ self.operation_dest = operation_dest
120
+
121
+ self._dest_url = None
122
+ self._legacy_sources = None
123
+
124
+ def __str__(self):
125
+ return '{sources}--{request_id}->{destination}'.format(
126
+ sources=','.join([str(s.rse) for s in self.sources]),
127
+ request_id=self.rws.request_id or '',
128
+ destination=self.dst.rse
129
+ )
130
+
131
+ @property
132
+ def src(self):
133
+ return self.sources[0]
134
+
135
+ @property
136
+ def dst(self):
137
+ return self.destination
138
+
139
+ @property
140
+ def dest_url(self):
141
+ if not self._dest_url:
142
+ self._dest_url = self._generate_dest_url(self.dst, self.rws, self.protocol_factory, self.operation_dest)
143
+ return self._dest_url
144
+
145
+ @property
146
+ def legacy_sources(self):
147
+ if not self._legacy_sources:
148
+ self._legacy_sources = [
149
+ (src.rse.name,
150
+ self._generate_source_url(src,
151
+ self.dst,
152
+ rws=self.rws,
153
+ protocol_factory=self.protocol_factory,
154
+ operation=self.operation_src),
155
+ src.rse.id,
156
+ src.ranking)
157
+ for src in self.sources
158
+ ]
159
+ return self._legacy_sources
160
+
161
+ @property
162
+ def use_ipv4(self):
163
+ # If any source or destination rse is ipv4 only
164
+ return self.dst.rse.attributes.get('use_ipv4', False) or any(src.rse.attributes.get('use_ipv4', False)
165
+ for src in self.sources)
166
+
167
+ @staticmethod
168
+ def __rewrite_source_url(source_url, source_sign_url, dest_sign_url, source_scheme):
169
+ """
170
+ Parametrize source url for some special cases of source and destination schemes
171
+ """
172
+ if dest_sign_url == 'gcs':
173
+ if source_scheme in ['davs', 'https']:
174
+ source_url += '?copy_mode=push'
175
+ elif dest_sign_url == 's3':
176
+ if source_scheme in ['davs', 'https']:
177
+ source_url += '?copy_mode=push'
178
+ elif WEBDAV_TRANSFER_MODE:
179
+ if source_scheme in ['davs', 'https']:
180
+ source_url += '?copy_mode=%s' % WEBDAV_TRANSFER_MODE
181
+
182
+ source_sign_url_map = {'gcs': 'gclouds', 's3': 's3s'}
183
+ if source_sign_url in source_sign_url_map:
184
+ if source_url[:7] == 'davs://':
185
+ source_url = source_sign_url_map[source_sign_url] + source_url[4:]
186
+ if source_url[:8] == 'https://':
187
+ source_url = source_sign_url_map[source_sign_url] + source_url[5:]
188
+
189
+ if source_url[:12] == 'srm+https://':
190
+ source_url = 'srm' + source_url[9:]
191
+ return source_url
192
+
193
+ @staticmethod
194
+ def __rewrite_dest_url(dest_url, dest_sign_url):
195
+ """
196
+ Parametrize destination url for some special cases of destination schemes
197
+ """
198
+ if dest_sign_url == 'gcs':
199
+ dest_url = re.sub('davs', 'gclouds', dest_url)
200
+ dest_url = re.sub('https', 'gclouds', dest_url)
201
+ elif dest_sign_url == 's3':
202
+ dest_url = re.sub('davs', 's3s', dest_url)
203
+ dest_url = re.sub('https', 's3s', dest_url)
204
+
205
+ if dest_url[:12] == 'srm+https://':
206
+ dest_url = 'srm' + dest_url[9:]
207
+ return dest_url
208
+
209
+ @classmethod
210
+ def _generate_source_url(cls, src: RequestSource, dst: TransferDestination, rws: RequestWithSources, protocol_factory: ProtocolFactory, operation: str):
211
+ """
212
+ Generate the source url which will be used as origin to copy the file from request rws towards the given dst endpoint
213
+ """
214
+ # Get source protocol
215
+ protocol = protocol_factory.protocol(src.rse, src.scheme, operation)
216
+
217
+ # Compute the source URL
218
+ source_sign_url = src.rse.attributes.get('sign_url', None)
219
+ dest_sign_url = dst.rse.attributes.get('sign_url', None)
220
+ source_url = list(protocol.lfns2pfns(lfns={'scope': rws.scope.external, 'name': rws.name, 'path': src.file_path}).values())[0]
221
+ source_url = cls.__rewrite_source_url(source_url, source_sign_url=source_sign_url, dest_sign_url=dest_sign_url, source_scheme=src.scheme)
222
+ return source_url
223
+
224
+ @classmethod
225
+ def _generate_dest_url(cls, dst: TransferDestination, rws: RequestWithSources, protocol_factory: ProtocolFactory, operation: str):
226
+ """
227
+ Generate the destination url for copying the file of request rws
228
+ """
229
+ # Get destination protocol
230
+ protocol = protocol_factory.protocol(dst.rse, dst.scheme, operation)
231
+
232
+ if dst.rse.info['deterministic']:
233
+ dest_url = list(protocol.lfns2pfns(lfns={'scope': rws.scope.external, 'name': rws.name}).values())[0]
234
+ else:
235
+ # compute dest url in case of non deterministic
236
+ # naming convention, etc.
237
+ dsn = get_dsn(rws.scope, rws.name, rws.attributes.get('dsn', None))
238
+ # DQ2 path always starts with /, but prefix might not end with /
239
+ naming_convention = dst.rse.attributes.get('naming_convention', None)
240
+ dest_path = construct_surl(dsn, rws.scope.external, rws.name, naming_convention)
241
+ if dst.rse.is_tape():
242
+ if rws.retry_count or rws.activity == 'Recovery':
243
+ dest_path = '%s_%i' % (dest_path, int(time.time()))
244
+
245
+ dest_url = list(protocol.lfns2pfns(lfns={'scope': rws.scope.external, 'name': rws.name, 'path': dest_path}).values())[0]
246
+
247
+ dest_sign_url = dst.rse.attributes.get('sign_url', None)
248
+ dest_url = cls.__rewrite_dest_url(dest_url, dest_sign_url=dest_sign_url)
249
+ return dest_url
250
+
251
+
252
+ class StageinTransferDefinition(DirectTransferDefinition):
253
+ """
254
+ A definition of a transfer which triggers a stagein operation.
255
+ - The source and destination url are identical
256
+ - must be from TAPE to non-TAPE RSE
257
+ - can only have one source
258
+ """
259
+ def __init__(self, source, destination, rws, protocol_factory, operation_src, operation_dest):
260
+ if not source.rse.is_tape() or destination.rse.is_tape():
261
+ # allow staging_required QoS RSE to be TAPE to TAPE for pin
262
+ if not destination.rse.attributes.get('staging_required', None):
263
+ raise RucioException("Stageing request {} must be from TAPE to DISK rse. Got {} and {}.".format(rws, source, destination))
264
+ super().__init__(source, destination, rws, protocol_factory, operation_src, operation_dest)
265
+
266
+ @property
267
+ def dest_url(self):
268
+ if not self._dest_url:
269
+ self._dest_url = self.src.url if self.src.url else self._generate_source_url(self.src,
270
+ self.dst,
271
+ rws=self.rws,
272
+ protocol_factory=self.protocol_factory,
273
+ operation=self.operation_dest)
274
+ return self._dest_url
275
+
276
+ @property
277
+ def legacy_sources(self):
278
+ if not self._legacy_sources:
279
+ self._legacy_sources = [(
280
+ self.src.rse.name,
281
+ self.dest_url, # Source and dest url is the same for stagein requests
282
+ self.src.rse.id,
283
+ self.src.ranking
284
+ )]
285
+ return self._legacy_sources
286
+
287
+
288
+ def transfer_path_str(transfer_path: "list[DirectTransferDefinition]") -> str:
289
+ """
290
+ an implementation of __str__ for a transfer path, which is a list of direct transfers, so not really an object
291
+ """
292
+ if not transfer_path:
293
+ return 'empty transfer path'
294
+
295
+ multi_tt = False
296
+ if len({hop.rws.transfertool for hop in transfer_path if hop.rws.transfertool}) > 1:
297
+ # The path relies on more than one transfertool
298
+ multi_tt = True
299
+
300
+ if len(transfer_path) == 1:
301
+ return str(transfer_path[0])
302
+
303
+ path_str = str(transfer_path[0].src.rse)
304
+ for hop in transfer_path:
305
+ path_str += '--{request_id}{transfertool}->{destination}'.format(
306
+ request_id=hop.rws.request_id or '',
307
+ transfertool=':{}'.format(hop.rws.transfertool) if multi_tt else '',
308
+ destination=hop.dst.rse,
309
+ )
310
+ return path_str
311
+
312
+
313
+ @transactional_session
314
+ def mark_submitting(
315
+ transfer: "DirectTransferDefinition",
316
+ external_host: str,
317
+ *,
318
+ logger: "Callable",
319
+ session: "Session",
320
+ ):
321
+ """
322
+ Mark a transfer as submitting
323
+
324
+ :param transfer: A transfer object
325
+ :param session: Database session to use.
326
+ """
327
+
328
+ log_str = 'PREPARING REQUEST %s DID %s:%s TO SUBMITTING STATE PREVIOUS %s FROM %s TO %s USING %s ' % (transfer.rws.request_id,
329
+ transfer.rws.scope,
330
+ transfer.rws.name,
331
+ transfer.rws.previous_attempt_id,
332
+ transfer.legacy_sources,
333
+ transfer.dest_url,
334
+ external_host)
335
+ logger(logging.DEBUG, "%s", log_str)
336
+
337
+ stmt = update(
338
+ models.Request
339
+ ).where(
340
+ models.Request.id == transfer.rws.request_id,
341
+ models.Request.state == RequestState.QUEUED
342
+ ).execution_options(
343
+ synchronize_session=False
344
+ ).values(
345
+ {
346
+ 'state': RequestState.SUBMITTING,
347
+ 'external_id': None,
348
+ 'external_host': external_host,
349
+ 'dest_url': transfer.dest_url,
350
+ 'submitted_at': datetime.datetime.utcnow(),
351
+ }
352
+ )
353
+ rowcount = session.execute(stmt).rowcount
354
+
355
+ if rowcount == 0:
356
+ raise RequestNotFound("Failed to prepare transfer: request %s does not exist or is not in queued state" % transfer.rws)
357
+
358
+
359
+ @transactional_session
360
+ def ensure_db_sources(
361
+ transfer_path: "list[DirectTransferDefinition]",
362
+ *,
363
+ logger: "Callable",
364
+ session: "Session",
365
+ ):
366
+ """
367
+ Ensure the needed DB source objects exist
368
+ """
369
+
370
+ desired_sources = []
371
+ for transfer in transfer_path:
372
+
373
+ for src_rse, src_url, src_rse_id, rank in transfer.legacy_sources:
374
+ common_source_attrs = {
375
+ "scope": transfer.rws.scope,
376
+ "name": transfer.rws.name,
377
+ "rse_id": src_rse_id,
378
+ "dest_rse_id": transfer.dst.rse.id,
379
+ "ranking": rank if rank else 0,
380
+ "bytes": transfer.rws.byte_count,
381
+ "url": src_url,
382
+ "is_using": True,
383
+ }
384
+
385
+ desired_sources.append({'request_id': transfer.rws.request_id, **common_source_attrs})
386
+ if len(transfer_path) > 1 and transfer is not transfer_path[-1]:
387
+ # For multihop transfers, each hop's source is also an initial transfer's source.
388
+ desired_sources.append({'request_id': transfer_path[-1].rws.request_id, **common_source_attrs})
389
+
390
+ for source in desired_sources:
391
+ stmt = update(
392
+ models.Source
393
+ ).where(
394
+ models.Source.request_id == source['request_id'],
395
+ models.Source.rse_id == source['rse_id']
396
+ ).execution_options(
397
+ synchronize_session=False
398
+ ).values(
399
+ is_using=True
400
+ )
401
+ src_rowcount = session.execute(stmt).rowcount
402
+ if src_rowcount == 0:
403
+ models.Source(**source).save(session=session, flush=False)
404
+
405
+
406
+ @transactional_session
407
+ def set_transfers_state(
408
+ transfers,
409
+ state: "RequestState",
410
+ submitted_at: datetime.datetime,
411
+ external_host: str,
412
+ external_id: str,
413
+ transfertool: str,
414
+ *,
415
+ session: "Session",
416
+ logger
417
+ ):
418
+ """
419
+ Update the transfer info of a request.
420
+ :param transfers: Dictionary containing request transfer info.
421
+ :param session: Database session to use.
422
+ """
423
+
424
+ logger(logging.INFO, 'Setting state(%s), transfertool(%s), external_host(%s) and eid(%s) for transfers: %s',
425
+ state.name, transfertool, external_host, external_id, ', '.join(t.rws.request_id for t in transfers))
426
+ try:
427
+ for transfer in transfers:
428
+ rws = transfer.rws
429
+ logger(logging.DEBUG, 'COPYING REQUEST %s DID %s:%s USING %s with state(%s) with eid(%s)' % (rws.request_id, rws.scope, rws.name, external_host, state, external_id))
430
+ stmt = update(
431
+ models.Request
432
+ ).where(
433
+ models.Request.id == transfer.rws.request_id,
434
+ models.Request.state == RequestState.SUBMITTING
435
+ ).execution_options(
436
+ synchronize_session=False
437
+ ).values(
438
+ {
439
+ models.Request.state: state,
440
+ models.Request.external_id: external_id,
441
+ models.Request.external_host: external_host,
442
+ models.Request.source_rse_id: transfer.src.rse.id,
443
+ models.Request.submitted_at: submitted_at,
444
+ models.Request.transfertool: transfertool,
445
+ }
446
+ )
447
+ rowcount = session.execute(stmt).rowcount
448
+
449
+ if rowcount == 0:
450
+ raise RucioException("%s: failed to set transfer state: request doesn't exist or is not in SUBMITTING state" % rws)
451
+
452
+ stmt = select(
453
+ models.DataIdentifier.datatype
454
+ ).where(
455
+ models.DataIdentifier.scope == rws.scope,
456
+ models.DataIdentifier.name == rws.name,
457
+ )
458
+ datatype = session.execute(stmt).scalar_one_or_none()
459
+
460
+ msg = {'request-id': rws.request_id,
461
+ 'request-type': rws.request_type,
462
+ 'scope': rws.scope.external,
463
+ 'name': rws.name,
464
+ 'dataset': None,
465
+ 'datasetScope': None,
466
+ 'src-rse-id': transfer.src.rse.id,
467
+ 'src-rse': transfer.src.rse.name,
468
+ 'dst-rse-id': transfer.dst.rse.id,
469
+ 'dst-rse': transfer.dst.rse.name,
470
+ 'state': state,
471
+ 'activity': rws.activity,
472
+ 'file-size': rws.byte_count,
473
+ 'bytes': rws.byte_count,
474
+ 'checksum-md5': rws.md5,
475
+ 'checksum-adler': rws.adler32,
476
+ 'external-id': external_id,
477
+ 'external-host': external_host,
478
+ 'queued_at': str(submitted_at),
479
+ 'datatype': datatype}
480
+ if rws.scope.vo != 'def':
481
+ msg['vo'] = rws.scope.vo
482
+
483
+ ds_scope = transfer.rws.attributes.get('ds_scope')
484
+ if ds_scope:
485
+ msg['datasetScope'] = ds_scope
486
+ ds_name = transfer.rws.attributes.get('ds_name')
487
+ if ds_name:
488
+ msg['dataset'] = ds_name
489
+
490
+ if msg['request-type']:
491
+ transfer_status = '%s-%s' % (msg['request-type'].name, msg['state'].name)
492
+ else:
493
+ transfer_status = 'transfer-%s' % msg['state']
494
+ transfer_status = transfer_status.lower()
495
+
496
+ message_core.add_message(transfer_status, msg, session=session)
497
+
498
+ except IntegrityError as error:
499
+ raise RucioException(error.args)
500
+
501
+ logger(logging.DEBUG, 'Finished to register transfer state for %s' % external_id)
502
+
503
+
504
+ @transactional_session
505
+ def update_transfer_state(tt_status_report: TransferStatusReport, *, session: "Session", logger=logging.log):
506
+ """
507
+ Used by poller and consumer to update the internal state of requests,
508
+ after the response by the external transfertool.
509
+
510
+ :param tt_status_report: The transfertool status update, retrieved via request.query_request().
511
+ :param session: The database session to use.
512
+ :param logger: Optional decorated logger that can be passed from the calling daemons or servers.
513
+ :returns commit_or_rollback: Boolean.
514
+ """
515
+
516
+ request_id = tt_status_report.request_id
517
+ try:
518
+ fields_to_update = tt_status_report.get_db_fields_to_update(session=session, logger=logger)
519
+ if not fields_to_update:
520
+ request_core.update_request(request_id, raise_on_missing=True, session=session)
521
+ return False
522
+ else:
523
+ logger(logging.INFO, 'UPDATING REQUEST %s FOR %s with changes: %s' % (str(request_id), tt_status_report, fields_to_update))
524
+
525
+ set_request_state(request_id, session=session, **fields_to_update)
526
+ request = tt_status_report.request(session)
527
+
528
+ if tt_status_report.state == RequestState.FAILED:
529
+ if request_core.is_intermediate_hop(request):
530
+ request_core.handle_failed_intermediate_hop(request, session=session)
531
+
532
+ request_core.add_monitor_message(
533
+ new_state=tt_status_report.state,
534
+ request=request,
535
+ additional_fields=tt_status_report.get_monitor_msg_fields(session=session, logger=logger),
536
+ session=session
537
+ )
538
+ return True
539
+ except UnsupportedOperation as error:
540
+ logger(logging.WARNING, "Request %s doesn't exist - Error: %s" % (request_id, str(error).replace('\n', '')))
541
+ return False
542
+ except Exception:
543
+ logger(logging.CRITICAL, "Exception", exc_info=True)
544
+
545
+
546
+ @transactional_session
547
+ def mark_transfer_lost(request, *, session: "Session", logger=logging.log):
548
+ new_state = RequestState.LOST
549
+ reason = "The FTS job lost"
550
+
551
+ err_msg = request_core.get_transfer_error(new_state, reason)
552
+ set_request_state(request['id'], state=new_state, external_id=request['external_id'], err_msg=err_msg, session=session, logger=logger)
553
+
554
+ request_core.add_monitor_message(new_state=new_state, request=request, additional_fields={'reason': reason}, session=session)
555
+
556
+
557
+ @METRICS.count_it
558
+ @transactional_session
559
+ def touch_transfer(external_host, transfer_id, *, session: "Session"):
560
+ """
561
+ Update the timestamp of requests in a transfer. Fails silently if the transfer_id does not exist.
562
+ :param request_host: Name of the external host.
563
+ :param transfer_id: External transfer job id as a string.
564
+ :param session: Database session to use.
565
+ """
566
+ try:
567
+ # don't touch it if it's already touched in 30 seconds
568
+ stmt = update(
569
+ models.Request
570
+ ).prefix_with(
571
+ "/*+ INDEX(REQUESTS REQUESTS_EXTERNALID_UQ) */", dialect='oracle'
572
+ ).where(
573
+ models.Request.external_id == transfer_id,
574
+ models.Request.state == RequestState.SUBMITTED,
575
+ models.Request.updated_at < datetime.datetime.utcnow() - datetime.timedelta(seconds=30)
576
+ ).execution_options(
577
+ synchronize_session=False
578
+ ).values(
579
+ updated_at=datetime.datetime.utcnow()
580
+ )
581
+ session.execute(stmt)
582
+ except IntegrityError as error:
583
+ raise RucioException(error.args)
584
+
585
+
586
+ @read_session
587
+ def __create_transfer_definitions(
588
+ topology: "Topology",
589
+ protocol_factory: ProtocolFactory,
590
+ rws: RequestWithSources,
591
+ sources: "list[RequestSource]",
592
+ max_sources: int,
593
+ multi_source_sources: "list[RequestSource]",
594
+ limit_dest_schemes: list[str],
595
+ operation_src: str,
596
+ operation_dest: str,
597
+ domain: str,
598
+ *,
599
+ session: "Session",
600
+ ) -> "dict[str, list[DirectTransferDefinition]]":
601
+ """
602
+ Find the all paths from sources towards the destination of the given transfer request.
603
+ Create the transfer definitions for each point-to-point transfer (multi-source, when possible)
604
+ """
605
+ shortest_paths = topology.search_shortest_paths(src_nodes=[s.rse for s in sources], dst_node=rws.dest_rse,
606
+ operation_src=operation_src, operation_dest=operation_dest,
607
+ domain=domain, limit_dest_schemes=limit_dest_schemes, session=session)
608
+
609
+ transfers_by_source = {}
610
+ sources_by_rse = {s.rse: s for s in sources}
611
+ paths_by_source = {sources_by_rse[rse]: path for rse, path in shortest_paths.items()}
612
+ for source, list_hops in paths_by_source.items():
613
+ transfer_path = []
614
+ for hop in list_hops:
615
+ hop_src_rse = hop['source_rse']
616
+ hop_dst_rse = hop['dest_rse']
617
+ src = RequestSource(
618
+ rse_data=hop_src_rse,
619
+ file_path=source.file_path if hop_src_rse == source.rse else None,
620
+ ranking=source.ranking if hop_src_rse == source.rse else 0,
621
+ distance=hop['cumulated_distance'] if hop_src_rse == source.rse else hop['hop_distance'],
622
+ scheme=hop['source_scheme'],
623
+ )
624
+ dst = TransferDestination(
625
+ rse_data=hop_dst_rse,
626
+ scheme=hop['dest_scheme'],
627
+ )
628
+ hop_definition = DirectTransferDefinition(
629
+ source=src,
630
+ destination=dst,
631
+ operation_src=operation_src,
632
+ operation_dest=operation_dest,
633
+ # keep the current rws for last hop; create a new one for other hops
634
+ rws=rws if hop_dst_rse == rws.dest_rse else RequestWithSources(
635
+ id_=None,
636
+ request_type=rws.request_type,
637
+ rule_id=None,
638
+ scope=rws.scope,
639
+ name=rws.name,
640
+ md5=rws.md5,
641
+ adler32=rws.adler32,
642
+ byte_count=rws.byte_count,
643
+ activity=rws.activity,
644
+ attributes={
645
+ 'activity': rws.activity,
646
+ 'source_replica_expression': None,
647
+ 'lifetime': None,
648
+ 'ds_scope': None,
649
+ 'ds_name': None,
650
+ 'bytes': rws.byte_count,
651
+ 'md5': rws.md5,
652
+ 'adler32': rws.adler32,
653
+ 'priority': None,
654
+ 'allow_tape_source': True
655
+ },
656
+ previous_attempt_id=None,
657
+ dest_rse_data=hop_dst_rse,
658
+ account=rws.account,
659
+ retry_count=0,
660
+ priority=rws.priority,
661
+ transfertool=rws.transfertool,
662
+ ),
663
+ protocol_factory=protocol_factory,
664
+ )
665
+
666
+ transfer_path.append(hop_definition)
667
+ transfers_by_source[source.rse.id] = transfer_path
668
+
669
+ # create multi-source transfers: add additional sources if possible
670
+ for transfer_path in transfers_by_source.values():
671
+ if len(transfer_path) == 1 and not transfer_path[0].src.rse.is_tape():
672
+ # Multiple single-hop DISK rses can be used together in "multi-source" transfers
673
+ #
674
+ # Try adding additional single-hop DISK rses sources to the transfer
675
+ main_source_schemes = __add_compatible_schemes(schemes=[transfer_path[0].dst.scheme], allowed_schemes=SUPPORTED_PROTOCOLS)
676
+ added_sources = 0
677
+ for source in sorted(multi_source_sources, key=lambda s: (-s.ranking, s.distance)):
678
+ if added_sources >= max_sources:
679
+ break
680
+
681
+ edge = topology.edge(source.rse, transfer_path[0].dst.rse)
682
+ if not edge:
683
+ # There is no direct connection between this source and the destination
684
+ continue
685
+
686
+ if source.rse == transfer_path[0].src.rse:
687
+ # This is the main source. Don't add a duplicate.
688
+ continue
689
+
690
+ if source.rse.is_tape():
691
+ continue
692
+
693
+ try:
694
+ matching_scheme = rsemgr.find_matching_scheme(
695
+ rse_settings_src=source.rse.info,
696
+ rse_settings_dest=transfer_path[0].dst.rse.info,
697
+ operation_src=operation_src,
698
+ operation_dest=operation_dest,
699
+ domain=domain,
700
+ scheme=main_source_schemes)
701
+ except RSEProtocolNotSupported:
702
+ continue
703
+
704
+ transfer_path[0].sources.append(
705
+ RequestSource(
706
+ rse_data=source.rse,
707
+ file_path=source.file_path,
708
+ ranking=source.ranking,
709
+ distance=edge.cost,
710
+ scheme=matching_scheme[1],
711
+ )
712
+ )
713
+ added_sources += 1
714
+ return transfers_by_source
715
+
716
+
717
+ def __create_stagein_definitions(
718
+ rws: RequestWithSources,
719
+ sources: "list[RequestSource]",
720
+ limit_dest_schemes: list[str],
721
+ operation_src: str,
722
+ operation_dest: str,
723
+ protocol_factory: ProtocolFactory,
724
+ ) -> "dict[str, list[StageinTransferDefinition]]":
725
+ """
726
+ for each source, create a single-hop transfer path with a one stageing definition inside
727
+ """
728
+ transfers_by_source = {
729
+ source.rse.id: [
730
+ StageinTransferDefinition(
731
+ source=RequestSource(
732
+ rse_data=source.rse,
733
+ file_path=source.file_path,
734
+ url=source.url,
735
+ scheme=limit_dest_schemes,
736
+ ),
737
+ destination=TransferDestination(
738
+ rse_data=rws.dest_rse,
739
+ scheme=limit_dest_schemes,
740
+ ),
741
+ operation_src=operation_src,
742
+ operation_dest=operation_dest,
743
+ rws=rws,
744
+ protocol_factory=protocol_factory,
745
+ )
746
+
747
+ ]
748
+ for source in sources
749
+ }
750
+ return transfers_by_source
751
+
752
+
753
+ def get_dsn(scope, name, dsn):
754
+ if dsn:
755
+ return dsn
756
+ # select a containing dataset
757
+ for parent in did.list_parent_dids(scope, name):
758
+ if parent['type'] == DIDType.DATASET:
759
+ return parent['name']
760
+ return 'other'
761
+
762
+
763
+ def __filter_multihops_with_intermediate_tape(candidate_paths: "Iterable[list[DirectTransferDefinition]]") -> "Generator[list[DirectTransferDefinition]]":
764
+ # Discard multihop transfers which contain a tape source as an intermediate hop
765
+ for path in candidate_paths:
766
+ if any(transfer.src.rse.is_tape_or_staging_required() for transfer in path[1:]):
767
+ pass
768
+ else:
769
+ yield path
770
+
771
+
772
+ def __compress_multihops(
773
+ candidate_paths: "Iterable[list[DirectTransferDefinition]]",
774
+ sources: "Iterable[RequestSource]",
775
+ ) -> "Generator[list[DirectTransferDefinition]]":
776
+ # Compress multihop transfers which contain other sources as part of itself.
777
+ # For example: multihop A->B->C and B is a source, compress A->B->C into B->C
778
+ source_rses = {s.rse.id for s in sources}
779
+ seen_source_rses = set()
780
+ for path in candidate_paths:
781
+ if len(path) > 1:
782
+ # find the index of the first hop starting from the end which is also a source. Path[0] will always be a source.
783
+ last_source_idx = next((idx for idx, hop in reversed(list(enumerate(path))) if hop.src.rse.id in source_rses), (0, None))
784
+ if last_source_idx > 0:
785
+ path = path[last_source_idx:]
786
+
787
+ # Deduplicate paths from same source
788
+ src_rse_id = path[0].src.rse.id
789
+ if src_rse_id not in seen_source_rses:
790
+ seen_source_rses.add(src_rse_id)
791
+ yield path
792
+
793
+
794
+ def __sort_paths(candidate_paths: "Iterable[list[DirectTransferDefinition]]") -> "Generator[list[DirectTransferDefinition]]":
795
+
796
+ def __transfer_order_key(transfer_path):
797
+ # Reduce the priority of the tape sources. If there are any disk sources,
798
+ # they must fail twice (1 penalty + 1 disk preferred over tape) before a tape will even be tried
799
+ source_ranking_penalty = 1 if transfer_path[0].src.rse.is_tape_or_staging_required() else 0
800
+ # higher source_ranking first,
801
+ # on equal source_ranking, prefer DISK over TAPE
802
+ # on equal type, prefer lower distance
803
+ # on equal distance, prefer single hop
804
+ return (
805
+ - transfer_path[0].src.ranking + source_ranking_penalty,
806
+ transfer_path[0].src.rse.is_tape_or_staging_required(), # rely on the fact that False < True
807
+ transfer_path[0].src.distance,
808
+ len(transfer_path) > 1, # rely on the fact that False < True
809
+ )
810
+
811
+ yield from sorted(candidate_paths, key=__transfer_order_key)
812
+
813
+
814
+ @transactional_session
815
+ def build_transfer_paths(
816
+ topology: "Topology",
817
+ protocol_factory: "ProtocolFactory",
818
+ requests_with_sources: "Iterable[RequestWithSources]",
819
+ admin_accounts: "Optional[set[InternalAccount]]" = None,
820
+ schemes: "Optional[list[str]]" = None,
821
+ failover_schemes: "Optional[list[str]]" = None,
822
+ max_sources: int = 4,
823
+ transfertools: "Optional[list[str]]" = None,
824
+ requested_source_only: bool = False,
825
+ preparer_mode: bool = False,
826
+ *,
827
+ session: "Session",
828
+ logger: "Callable" = logging.log,
829
+ ):
830
+ """
831
+ For each request, find all possible transfer paths from its sources, which respect the
832
+ constraints enforced by the request (attributes, type, etc) and the arguments of this function
833
+
834
+ build a multi-source transfer if possible: The scheme compatibility is important for multi-source transfers.
835
+ We iterate again over the single-hop sources and build a new transfer definition while enforcing the scheme compatibility
836
+ with the initial source.
837
+
838
+ Each path is a list of hops. Each hop is a transfer definition.
839
+ """
840
+ if schemes is None:
841
+ schemes = []
842
+
843
+ if failover_schemes is None:
844
+ failover_schemes = []
845
+
846
+ if admin_accounts is None:
847
+ admin_accounts = set()
848
+
849
+ # Do not print full source RSE list for DIDs which have many sources. Otherwise we fill the monitoring
850
+ # storage with data which has little to no benefit. This log message is unlikely to help debugging
851
+ # transfers issues when there are many sources, but can be very useful for small number of sources.
852
+ num_sources_in_logs = 4
853
+
854
+ candidate_paths_by_request_id, reqs_no_source, reqs_only_tape_source, reqs_scheme_mismatch = {}, set(), set(), set()
855
+ reqs_unsupported_transfertool = set()
856
+ for rws in requests_with_sources:
857
+
858
+ rws.dest_rse.ensure_loaded(load_name=True, load_info=True, load_attributes=True, load_columns=True, session=session)
859
+ all_sources = rws.sources
860
+ for source in all_sources:
861
+ source.rse.ensure_loaded(load_name=True, load_info=True, load_attributes=True, load_columns=True, session=session)
862
+
863
+ transfer_schemes = schemes
864
+ if rws.previous_attempt_id and failover_schemes:
865
+ transfer_schemes = failover_schemes
866
+
867
+ # Assume request doesn't have any sources. Will be removed later if sources are found.
868
+ reqs_no_source.add(rws.request_id)
869
+ if not all_sources:
870
+ logger(logging.INFO, '%s: has no sources. Skipping.', rws)
871
+ continue
872
+
873
+ logger(logging.DEBUG, '%s: Working on %d sources%s: %s%s',
874
+ rws,
875
+ len(all_sources),
876
+ f' (priority {rws.requested_source.rse})' if requested_source_only and rws.requested_source else '',
877
+ ','.join('{}:{}:{}'.format(src.rse, src.ranking, src.distance) for src in all_sources[:num_sources_in_logs]),
878
+ '... and %d others' % (len(all_sources) - num_sources_in_logs) if len(all_sources) > num_sources_in_logs else '')
879
+
880
+ # Check if destination is blocked
881
+ if not (topology.ignore_availability or rws.dest_rse.columns['availability_write']):
882
+ logger(logging.WARNING, '%s: dst RSE is blocked for write. Will skip the submission of new jobs', rws.request_id)
883
+ continue
884
+ if rws.account not in admin_accounts and rws.dest_rse.attributes.get('restricted_write'):
885
+ logger(logging.WARNING, '%s: dst RSE is restricted for write. Will skip the submission', rws.request_id)
886
+ continue
887
+
888
+ if rws.transfertool and transfertools and rws.transfertool not in transfertools:
889
+ # The request explicitly asks for a transfertool which this submitter doesn't support
890
+ logger(logging.INFO, '%s: unsupported transfertool. Skipping.', rws.request_id)
891
+ reqs_unsupported_transfertool.add(rws.request_id)
892
+ reqs_no_source.remove(rws.request_id)
893
+ continue
894
+
895
+ # parse source expression
896
+ source_replica_expression = rws.attributes.get('source_replica_expression', None)
897
+ allowed_source_rses = None
898
+ if source_replica_expression:
899
+ try:
900
+ parsed_rses = parse_expression(source_replica_expression, session=session)
901
+ except InvalidRSEExpression as error:
902
+ logger(logging.ERROR, "%s: Invalid RSE exception %s: %s", rws.request_id, source_replica_expression, str(error))
903
+ continue
904
+ else:
905
+ allowed_source_rses = [x['id'] for x in parsed_rses]
906
+
907
+ filtered_sources = all_sources
908
+ # Only keep allowed sources
909
+ if allowed_source_rses is not None:
910
+ filtered_sources = filter(lambda s: s.rse.id in allowed_source_rses, filtered_sources)
911
+ filtered_sources = filter(lambda s: s.rse.name is not None, filtered_sources)
912
+ if rws.account not in admin_accounts:
913
+ filtered_sources = filter(lambda s: not s.rse.attributes.get('restricted_read'), filtered_sources)
914
+ # Ignore blocklisted RSEs
915
+ if not topology.ignore_availability:
916
+ filtered_sources = filter(lambda s: s.rse.columns['availability_read'], filtered_sources)
917
+ # For staging requests, the staging_buffer attribute must be correctly set
918
+ if rws.request_type == RequestType.STAGEIN:
919
+ filtered_sources = filter(lambda s: s.rse.attributes.get('staging_buffer') == rws.dest_rse.name, filtered_sources)
920
+ # Ignore tape sources if they are not desired
921
+ filtered_sources = list(filtered_sources)
922
+ had_tape_sources = len(filtered_sources) > 0
923
+ if not rws.attributes.get("allow_tape_source", True):
924
+ filtered_sources = filter(lambda s: not s.rse.is_tape_or_staging_required(), filtered_sources)
925
+
926
+ filtered_sources = list(filtered_sources)
927
+ filtered_rses_log = ''
928
+ if len(all_sources) != len(filtered_sources):
929
+ filtered_rses = list(set(s.rse.name for s in all_sources).difference(s.rse.name for s in filtered_sources))
930
+ filtered_rses_log = '; %d dropped by filter: ' % (len(all_sources) - len(filtered_sources))
931
+ filtered_rses_log += ','.join(filtered_rses[:num_sources_in_logs])
932
+ if len(filtered_rses) > num_sources_in_logs:
933
+ filtered_rses_log += '... and %d others' % (len(filtered_rses) - num_sources_in_logs)
934
+ candidate_paths = []
935
+
936
+ candidate_sources = filtered_sources
937
+ if requested_source_only and rws.requested_source:
938
+ candidate_sources = [rws.requested_source] if rws.requested_source in filtered_sources else []
939
+
940
+ if rws.request_type == RequestType.STAGEIN:
941
+ paths = __create_stagein_definitions(rws=rws,
942
+ sources=candidate_sources,
943
+ limit_dest_schemes=transfer_schemes,
944
+ operation_src='read',
945
+ operation_dest='write',
946
+ protocol_factory=protocol_factory)
947
+ else:
948
+ paths = __create_transfer_definitions(topology=topology,
949
+ rws=rws,
950
+ sources=candidate_sources,
951
+ max_sources=max_sources,
952
+ multi_source_sources=[] if preparer_mode else filtered_sources,
953
+ limit_dest_schemes=[],
954
+ operation_src='third_party_copy_read',
955
+ operation_dest='third_party_copy_write',
956
+ domain='wan',
957
+ protocol_factory=protocol_factory,
958
+ session=session)
959
+
960
+ sources_without_path = []
961
+ any_source_had_scheme_mismatch = False
962
+ for source in candidate_sources:
963
+ transfer_path = paths.get(source.rse.id)
964
+ if transfer_path is None:
965
+ logger(logging.WARNING, "%s: no path from %s to %s", rws.request_id, source.rse, rws.dest_rse)
966
+ sources_without_path.append(source.rse.name)
967
+ continue
968
+ if not transfer_path:
969
+ any_source_had_scheme_mismatch = True
970
+ logger(logging.WARNING, "%s: no matching protocol between %s and %s", rws.request_id, source.rse, rws.dest_rse)
971
+ sources_without_path.append(source.rse.name)
972
+ continue
973
+
974
+ if len(transfer_path) > 1:
975
+ logger(logging.DEBUG, '%s: From %s to %s requires multihop: %s', rws.request_id, source.rse, rws.dest_rse, transfer_path_str(transfer_path))
976
+
977
+ candidate_paths.append(transfer_path)
978
+
979
+ if len(candidate_sources) != len(candidate_paths):
980
+ logger(logging.DEBUG, '%s: Sources after path computation: %s', rws.request_id, [str(path[0].src.rse) for path in candidate_paths])
981
+
982
+ sources_without_path_log = ''
983
+ if sources_without_path:
984
+ sources_without_path_log = '; %d dropped due to missing path: ' % len(sources_without_path)
985
+ sources_without_path_log += ','.join(sources_without_path[:num_sources_in_logs])
986
+ if len(sources_without_path) > num_sources_in_logs:
987
+ sources_without_path_log += '... and %d others' % (len(sources_without_path) - num_sources_in_logs)
988
+
989
+ candidate_paths = __filter_multihops_with_intermediate_tape(candidate_paths)
990
+ if not preparer_mode:
991
+ candidate_paths = __compress_multihops(candidate_paths, all_sources)
992
+ candidate_paths = list(__sort_paths(candidate_paths))
993
+
994
+ ordered_sources_log = ','.join(('multihop: ' if len(path) > 1 else '') + '{}:{}:{}'.format(path[0].src.rse, path[0].src.ranking, path[0].src.distance)
995
+ for path in candidate_paths[:num_sources_in_logs])
996
+ if len(candidate_paths) > num_sources_in_logs:
997
+ ordered_sources_log += '... and %d others' % (len(candidate_paths) - num_sources_in_logs)
998
+
999
+ logger(logging.INFO, '%s: %d ordered sources: %s%s%s', rws, len(candidate_paths),
1000
+ ordered_sources_log, filtered_rses_log, sources_without_path_log)
1001
+
1002
+ if not candidate_paths:
1003
+ # It can happen that some sources are skipped because they are TAPE, and others because
1004
+ # of scheme mismatch. However, we can only have one state in the database. I picked to
1005
+ # prioritize setting only_tape_source without any particular reason.
1006
+ if had_tape_sources and not filtered_sources:
1007
+ logger(logging.DEBUG, '%s: Only tape sources found' % rws.request_id)
1008
+ reqs_only_tape_source.add(rws.request_id)
1009
+ reqs_no_source.remove(rws.request_id)
1010
+ elif any_source_had_scheme_mismatch:
1011
+ logger(logging.DEBUG, '%s: Scheme mismatch detected' % rws.request_id)
1012
+ reqs_scheme_mismatch.add(rws.request_id)
1013
+ reqs_no_source.remove(rws.request_id)
1014
+ else:
1015
+ logger(logging.DEBUG, '%s: No candidate path found' % rws.request_id)
1016
+ continue
1017
+
1018
+ candidate_paths_by_request_id[rws.request_id] = candidate_paths
1019
+ reqs_no_source.remove(rws.request_id)
1020
+
1021
+ return candidate_paths_by_request_id, reqs_no_source, reqs_scheme_mismatch, reqs_only_tape_source, reqs_unsupported_transfertool
1022
+
1023
+
1024
+ def __add_compatible_schemes(schemes, allowed_schemes):
1025
+ """
1026
+ Add the compatible schemes to a list of schemes
1027
+ :param schemes: Schemes as input.
1028
+ :param allowed_schemes: Allowed schemes, only these can be in the output.
1029
+ :returns: List of schemes
1030
+ """
1031
+
1032
+ return_schemes = []
1033
+ for scheme in schemes:
1034
+ if scheme in allowed_schemes:
1035
+ return_schemes.append(scheme)
1036
+ for scheme_map_scheme in constants.SCHEME_MAP.get(scheme, []):
1037
+ if scheme_map_scheme not in allowed_schemes:
1038
+ continue
1039
+ else:
1040
+ return_schemes.append(scheme_map_scheme)
1041
+ return list(set(return_schemes))
1042
+
1043
+
1044
+ @read_session
1045
+ def list_transfer_admin_accounts(*, session: "Session") -> "set[InternalAccount]":
1046
+ """
1047
+ List admin accounts and cache the result in memory
1048
+ """
1049
+
1050
+ result = REGION_ACCOUNTS.get('transfer_admin_accounts')
1051
+ if isinstance(result, NoValue):
1052
+ result = [acc['account'] for acc in list_accounts(filter_={'admin': True}, session=session)]
1053
+ REGION_ACCOUNTS.set('transfer_admin_accounts', result)
1054
+ return set(result)
1055
+
1056
+
1057
+ def update_transfer_priority(transfers_to_update, logger=logging.log):
1058
+ """
1059
+ Update transfer priority in fts
1060
+
1061
+ :param transfers_to_update: dict {external_host1: {transfer_id1: priority, transfer_id2: priority, ...}, ...}
1062
+ :param logger: decorated logger instance
1063
+ """
1064
+
1065
+ for external_host, priority_by_transfer_id in transfers_to_update.items():
1066
+ transfertool_obj = FTS3Transfertool(external_host=external_host)
1067
+ for transfer_id, priority in priority_by_transfer_id.items():
1068
+ res = transfertool_obj.update_priority(transfer_id=transfer_id, priority=priority)
1069
+ logger(logging.DEBUG, "Updated transfer %s priority in transfertool to %s: %s" % (transfer_id, priority, res['http_message']))
1070
+
1071
+
1072
+ def cancel_transfers(transfers_to_cancel, logger=logging.log):
1073
+ """
1074
+ Cancel transfers in fts
1075
+
1076
+ :param transfers_to_cancel: dict {external_host1: {transfer_id1, transfer_id2}, external_host2: [...], ...}
1077
+ :param logger: decorated logger instance
1078
+ """
1079
+
1080
+ for external_host, transfer_ids in transfers_to_cancel.items():
1081
+ transfertool_obj = FTS3Transfertool(external_host=external_host)
1082
+ for transfer_id in transfer_ids:
1083
+ try:
1084
+ transfertool_obj.cancel(transfer_ids=[transfer_id])
1085
+ logger(logging.DEBUG, "Cancelled FTS3 transfer %s on %s" % (transfer_id, transfertool_obj))
1086
+ except Exception as error:
1087
+ logger(logging.WARNING, 'Could not cancel FTS3 transfer %s on %s: %s' % (transfer_id, transfertool_obj, str(error)))
1088
+
1089
+
1090
+ @METRICS.count_it
1091
+ def cancel_transfer(transfertool_obj, transfer_id):
1092
+ """
1093
+ Cancel a transfer based on external transfer id.
1094
+
1095
+ :param transfertool_obj: Transfertool object to be used for cancellation.
1096
+ :param transfer_id: External-ID as a 32 character hex string.
1097
+ """
1098
+
1099
+ try:
1100
+ transfertool_obj.cancel(transfer_ids=[transfer_id])
1101
+ except Exception:
1102
+ raise RucioException('Could not cancel FTS3 transfer %s on %s: %s' % (transfer_id, transfertool_obj, traceback.format_exc()))
1103
+
1104
+
1105
+ @transactional_session
1106
+ def prepare_transfers(
1107
+ candidate_paths_by_request_id: "dict[str, list[list[DirectTransferDefinition]]]",
1108
+ logger: "LoggerFunction" = logging.log,
1109
+ transfertools: "Optional[list[str]]" = None,
1110
+ *,
1111
+ session: "Session",
1112
+ ) -> tuple[list[str], list[str]]:
1113
+ """
1114
+ Update transfer requests according to preparer settings.
1115
+ """
1116
+
1117
+ reqs_no_transfertool = []
1118
+ updated_reqs = []
1119
+ for request_id, candidate_paths in candidate_paths_by_request_id.items():
1120
+ selected_source = None
1121
+ transfertool = None
1122
+ rws = candidate_paths[0][-1].rws
1123
+
1124
+ for candidate_path in candidate_paths:
1125
+ source = candidate_path[0].src
1126
+ all_hops_ok = True
1127
+ transfertool = None
1128
+ for hop in candidate_path:
1129
+ common_transfertools = get_supported_transfertools(hop.src.rse, hop.dst.rse, transfertools=transfertools, session=session)
1130
+ if not common_transfertools:
1131
+ all_hops_ok = False
1132
+ break
1133
+ # We need the last hop transfertool. Always prioritize fts3 if it exists.
1134
+ transfertool = 'fts3' if 'fts3' in common_transfertools else common_transfertools.pop()
1135
+
1136
+ if all_hops_ok and transfertool:
1137
+ selected_source = source
1138
+ break
1139
+
1140
+ if not selected_source:
1141
+ reqs_no_transfertool.append(request_id)
1142
+ logger(logging.WARNING, '%s: all available sources were filtered', rws)
1143
+ continue
1144
+
1145
+ update_dict: dict[Any, Any] = {
1146
+ models.Request.state.name: _throttler_request_state(
1147
+ activity=rws.activity,
1148
+ source_rse=selected_source.rse,
1149
+ dest_rse=rws.dest_rse,
1150
+ session=session,
1151
+ ),
1152
+ models.Request.source_rse_id.name: selected_source.rse.id,
1153
+ }
1154
+ if transfertool:
1155
+ update_dict[models.Request.transfertool.name] = transfertool
1156
+
1157
+ request_core.update_request(rws.request_id, session=session, **update_dict)
1158
+ updated_reqs.append(request_id)
1159
+
1160
+ return updated_reqs, reqs_no_transfertool
1161
+
1162
+
1163
+ @stream_session
1164
+ def applicable_rse_transfer_limits(
1165
+ source_rse: "Optional[RseData]" = None,
1166
+ dest_rse: "Optional[RseData]" = None,
1167
+ activity: "Optional[str]" = None,
1168
+ *,
1169
+ session: "Session",
1170
+ ):
1171
+ """
1172
+ Find all RseTransferLimits which must be enforced for transfers between source and destination RSEs for the given activity.
1173
+ """
1174
+ source_limits = {}
1175
+ if source_rse:
1176
+ source_limits = source_rse.ensure_loaded(load_transfer_limits=True, session=session).transfer_limits.get(TransferLimitDirection.SOURCE, {})
1177
+ dest_limits = {}
1178
+ if dest_rse:
1179
+ dest_limits = dest_rse.ensure_loaded(load_transfer_limits=True, session=session).transfer_limits.get(TransferLimitDirection.DESTINATION, {})
1180
+
1181
+ if activity is not None:
1182
+ limit = source_limits.get(activity)
1183
+ if limit:
1184
+ yield limit
1185
+
1186
+ limit = dest_limits.get(activity)
1187
+ if limit:
1188
+ yield limit
1189
+
1190
+ # get "all_activities" limits
1191
+ limit = source_limits.get(None)
1192
+ if limit:
1193
+ yield limit
1194
+
1195
+ limit = dest_limits.get(None)
1196
+ if limit:
1197
+ yield limit
1198
+
1199
+
1200
+ def _throttler_request_state(activity, source_rse, dest_rse, *, session: "Session") -> RequestState:
1201
+ """
1202
+ Takes request attributes to return a new state for the request
1203
+ based on throttler settings. Always returns QUEUED,
1204
+ if the throttler mode is not set.
1205
+ """
1206
+ limit_found = False
1207
+ if any(applicable_rse_transfer_limits(activity=activity, source_rse=source_rse, dest_rse=dest_rse, session=session)):
1208
+ limit_found = True
1209
+
1210
+ return RequestState.WAITING if limit_found else RequestState.QUEUED
1211
+
1212
+
1213
+ @read_session
1214
+ def get_supported_transfertools(
1215
+ source_rse: "RseData",
1216
+ dest_rse: "RseData",
1217
+ transfertools: "Optional[list[str]]" = None,
1218
+ *,
1219
+ session: "Session",
1220
+ ) -> set[str]:
1221
+
1222
+ if not transfertools:
1223
+ transfertools = list(TRANSFERTOOL_CLASSES_BY_NAME)
1224
+
1225
+ source_rse.ensure_loaded(load_attributes=True, session=session)
1226
+ dest_rse.ensure_loaded(load_attributes=True, session=session)
1227
+
1228
+ result = set()
1229
+ for tt_name in transfertools:
1230
+ tt_class = TRANSFERTOOL_CLASSES_BY_NAME.get(tt_name)
1231
+ if tt_class and tt_class.can_perform_transfer(source_rse, dest_rse):
1232
+ result.add(tt_name)
1233
+ return result